mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-25 17:43:35 +05:00
Compare commits
5 Commits
1.5.3
...
8834939969
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8834939969 | ||
|
|
2ffcda58ac | ||
|
|
0824066897 | ||
|
|
80b91d9a01 | ||
|
|
25b70e4db5 |
@@ -9,6 +9,30 @@ public:
|
|||||||
delete this->instance;
|
delete this->instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ReadResult{
|
||||||
|
bool valid = false;
|
||||||
|
bool parityValid = false;
|
||||||
|
bool responseMessageIdValid = false;
|
||||||
|
const char* responseType = "";
|
||||||
|
uint16_t value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
ReadResult readRequest(byte messageId) {
|
||||||
|
ReadResult result;
|
||||||
|
OpenThermMessageID eMessageId = (OpenThermMessageID)messageId;
|
||||||
|
auto response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||||
|
OpenThermRequestType::READ_DATA,
|
||||||
|
eMessageId,
|
||||||
|
0
|
||||||
|
));
|
||||||
|
result.valid = CustomOpenTherm::isValidResponse(response);
|
||||||
|
result.parityValid = !CustomOpenTherm::parity(response);
|
||||||
|
result.responseMessageIdValid = CustomOpenTherm::isValidResponseId(response, eMessageId);
|
||||||
|
result.responseType = CustomOpenTherm::messageTypeToString(CustomOpenTherm::getMessageType(response));
|
||||||
|
result.value = CustomOpenTherm::getUInt(response);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const unsigned short readyTime = 60000u;
|
const unsigned short readyTime = 60000u;
|
||||||
const unsigned int resetBusInterval = 120000u;
|
const unsigned int resetBusInterval = 120000u;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using namespace NetworkUtils;
|
|||||||
extern NetworkMgr* network;
|
extern NetworkMgr* network;
|
||||||
extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
|
extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
|
||||||
extern MqttTask* tMqtt;
|
extern MqttTask* tMqtt;
|
||||||
|
extern OpenThermTask* tOt;
|
||||||
|
|
||||||
|
|
||||||
class PortalTask : public LeanTask {
|
class PortalTask : public LeanTask {
|
||||||
@@ -177,6 +178,18 @@ protected:
|
|||||||
});
|
});
|
||||||
this->webServer->addHandler(upgradePage);
|
this->webServer->addHandler(upgradePage);
|
||||||
|
|
||||||
|
// Opentherm request page
|
||||||
|
auto openthermRequestPage = (new StaticPage("/opentherm_request.html", &LittleFS, F("/pages/opentherm_request.html"), PORTAL_CACHE))
|
||||||
|
->setBeforeSendCallback([this]() {
|
||||||
|
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||||
|
this->webServer->requestAuthentication(BASIC_AUTH);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
this->webServer->addHandler(openthermRequestPage);
|
||||||
|
|
||||||
// OTA
|
// OTA
|
||||||
auto upgradeHandler = (new UpgradeHandler("/api/upgrade"))->setCanUploadCallback([this](const String& uri) {
|
auto upgradeHandler = (new UpgradeHandler("/api/upgrade"))->setCanUploadCallback([this](const String& uri) {
|
||||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||||
@@ -841,6 +854,41 @@ protected:
|
|||||||
this->bufferedWebServer->send(200, F("application/json"), doc, true);
|
this->bufferedWebServer->send(200, F("application/json"), doc, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this->webServer->on(F("/api/opentherm_request/read"), HTTP_POST, [this]() {
|
||||||
|
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||||
|
return this->webServer->send(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& plain = this->webServer->arg(0);
|
||||||
|
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/opentherm_request/read %d bytes: %s"), plain.length(), plain.c_str());
|
||||||
|
|
||||||
|
JsonDocument doc;
|
||||||
|
DeserializationError dErr = deserializeJson(doc, plain);
|
||||||
|
|
||||||
|
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
|
||||||
|
this->webServer->send(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc[FPSTR(S_MESSAGE_ID)].isNull() || !doc[FPSTR(S_MESSAGE_ID)].is<byte>()) {
|
||||||
|
this->webServer->send(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto messageId = doc[FPSTR(S_MESSAGE_ID)].as<byte>();
|
||||||
|
doc.clear();
|
||||||
|
doc.shrinkToFit();
|
||||||
|
|
||||||
|
auto result = tOt->readRequest(messageId);
|
||||||
|
|
||||||
|
doc[FPSTR(S_VALID)] = result.valid;
|
||||||
|
doc[FPSTR(S_PARITY_VALID)] = result.parityValid;
|
||||||
|
doc[FPSTR(S_RESPONSE_MESSAGE_ID_VALID)] = result.responseMessageIdValid;
|
||||||
|
doc[FPSTR(S_RESPONSE_TYPE)] = result.responseType;
|
||||||
|
doc[FPSTR(S_VALUE)] = result.value;
|
||||||
|
doc.shrinkToFit();
|
||||||
|
|
||||||
|
this->bufferedWebServer->send(200, F("application/json"), doc);
|
||||||
|
});
|
||||||
|
|
||||||
// not found
|
// not found
|
||||||
this->webServer->onNotFound([this]() {
|
this->webServer->onNotFound([this]() {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef DEFAULT_HOSTNAME
|
#ifndef DEFAULT_HOSTNAME
|
||||||
#define DEFAULT_HOSTNAME "opentherm"
|
#define DEFAULT_HOSTNAME ""
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef DEFAULT_AP_SSID
|
#ifndef DEFAULT_AP_SSID
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef DEFAULT_MQTT_PREFIX
|
#ifndef DEFAULT_MQTT_PREFIX
|
||||||
#define DEFAULT_MQTT_PREFIX "opentherm"
|
#define DEFAULT_MQTT_PREFIX ""
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef DEFAULT_OT_IN_GPIO
|
#ifndef DEFAULT_OT_IN_GPIO
|
||||||
|
|||||||
12
src/main.cpp
12
src/main.cpp
@@ -102,6 +102,12 @@ void setup() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate hostname if it is empty
|
||||||
|
if (!strlen(networkSettings.hostname)) {
|
||||||
|
strcpy(networkSettings.hostname, getChipId("otgateway-").c_str());
|
||||||
|
fsNetworkSettings.update();
|
||||||
|
}
|
||||||
|
|
||||||
network = (new NetworkMgr)
|
network = (new NetworkMgr)
|
||||||
->setHostname(networkSettings.hostname)
|
->setHostname(networkSettings.hostname)
|
||||||
->setStaCredentials(
|
->setStaCredentials(
|
||||||
@@ -148,6 +154,12 @@ void setup() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate mqtt prefix if it is empty
|
||||||
|
if (!strlen(settings.mqtt.prefix)) {
|
||||||
|
strcpy(settings.mqtt.prefix, getChipId("otgateway_").c_str());
|
||||||
|
fsSettings.update();
|
||||||
|
}
|
||||||
|
|
||||||
// Logs settings
|
// Logs settings
|
||||||
if (!settings.system.serial.enabled) {
|
if (!settings.system.serial.enabled) {
|
||||||
Serial.end();
|
Serial.end();
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ const char S_MAX_POWER[] PROGMEM = "maxPower";
|
|||||||
const char S_MAX_TEMP[] PROGMEM = "maxTemp";
|
const char S_MAX_TEMP[] PROGMEM = "maxTemp";
|
||||||
const char S_MAX_TEMP_SYNC_WITH_TARGET_TEMP[] PROGMEM = "maxTempSyncWithTargetTemp";
|
const char S_MAX_TEMP_SYNC_WITH_TARGET_TEMP[] PROGMEM = "maxTempSyncWithTargetTemp";
|
||||||
const char S_MEMBER_ID[] PROGMEM = "memberId";
|
const char S_MEMBER_ID[] PROGMEM = "memberId";
|
||||||
|
const char S_MESSAGE_ID[] PROGMEM = "messageId";
|
||||||
const char S_MIN[] PROGMEM = "min";
|
const char S_MIN[] PROGMEM = "min";
|
||||||
const char S_MIN_FREE[] PROGMEM = "minFree";
|
const char S_MIN_FREE[] PROGMEM = "minFree";
|
||||||
const char S_MIN_MAX_FREE_BLOCK[] PROGMEM = "minMaxFreeBlock";
|
const char S_MIN_MAX_FREE_BLOCK[] PROGMEM = "minMaxFreeBlock";
|
||||||
@@ -147,6 +148,7 @@ const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
|
|||||||
const char S_OUT_GPIO[] PROGMEM = "outGpio";
|
const char S_OUT_GPIO[] PROGMEM = "outGpio";
|
||||||
const char S_OUTPUT[] PROGMEM = "output";
|
const char S_OUTPUT[] PROGMEM = "output";
|
||||||
const char S_PASSWORD[] PROGMEM = "password";
|
const char S_PASSWORD[] PROGMEM = "password";
|
||||||
|
const char S_PARITY_VALID[] PROGMEM = "parityValid";
|
||||||
const char S_PID[] PROGMEM = "pid";
|
const char S_PID[] PROGMEM = "pid";
|
||||||
const char S_PORT[] PROGMEM = "port";
|
const char S_PORT[] PROGMEM = "port";
|
||||||
const char S_PORTAL[] PROGMEM = "portal";
|
const char S_PORTAL[] PROGMEM = "portal";
|
||||||
@@ -163,6 +165,8 @@ const char S_RESET_DIAGNOSTIC[] PROGMEM = "resetDiagnostic";
|
|||||||
const char S_RESET_FAULT[] PROGMEM = "resetFault";
|
const char S_RESET_FAULT[] PROGMEM = "resetFault";
|
||||||
const char S_RESET_REASON[] PROGMEM = "resetReason";
|
const char S_RESET_REASON[] PROGMEM = "resetReason";
|
||||||
const char S_RESTART[] PROGMEM = "restart";
|
const char S_RESTART[] PROGMEM = "restart";
|
||||||
|
const char S_RESPONSE_MESSAGE_ID_VALID[] PROGMEM = "responseMessageIdValid";
|
||||||
|
const char S_RESPONSE_TYPE[] PROGMEM = "responseType";
|
||||||
const char S_RETURN_TEMP[] PROGMEM = "returnTemp";
|
const char S_RETURN_TEMP[] PROGMEM = "returnTemp";
|
||||||
const char S_REV[] PROGMEM = "rev";
|
const char S_REV[] PROGMEM = "rev";
|
||||||
const char S_RSSI[] PROGMEM = "rssi";
|
const char S_RSSI[] PROGMEM = "rssi";
|
||||||
@@ -203,5 +207,6 @@ const char S_UPTIME[] PROGMEM = "uptime";
|
|||||||
const char S_USE[] PROGMEM = "use";
|
const char S_USE[] PROGMEM = "use";
|
||||||
const char S_USE_DHCP[] PROGMEM = "useDhcp";
|
const char S_USE_DHCP[] PROGMEM = "useDhcp";
|
||||||
const char S_USER[] PROGMEM = "user";
|
const char S_USER[] PROGMEM = "user";
|
||||||
|
const char S_VALID[] PROGMEM = "valid";
|
||||||
const char S_VALUE[] PROGMEM = "value";
|
const char S_VALUE[] PROGMEM = "value";
|
||||||
const char S_VERSION[] PROGMEM = "version";
|
const char S_VERSION[] PROGMEM = "version";
|
||||||
|
|||||||
34
src/utils.h
34
src/utils.h
@@ -1,5 +1,37 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
String getChipId(const char* prefix = nullptr, const char* suffix = nullptr) {
|
||||||
|
String chipId;
|
||||||
|
chipId.reserve(
|
||||||
|
6
|
||||||
|
+ (prefix != nullptr ? strlen(prefix) : 0)
|
||||||
|
+ (suffix != nullptr ? strlen(suffix) : 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (prefix != nullptr) {
|
||||||
|
chipId.concat(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cid = 0;
|
||||||
|
#if defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
cid = ESP.getChipId();
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
|
// https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/ChipID/GetChipID/GetChipID.ino
|
||||||
|
for (uint8_t i = 0; i < 17; i = i + 8) {
|
||||||
|
cid |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
chipId += String(cid, HEX);
|
||||||
|
|
||||||
|
if (suffix != nullptr) {
|
||||||
|
chipId.concat(suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
chipId.trim();
|
||||||
|
return chipId;
|
||||||
|
}
|
||||||
|
|
||||||
bool isLeapYear(short year) {
|
bool isLeapYear(short year) {
|
||||||
if (year % 4 != 0) {
|
if (year % 4 != 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1791,7 +1823,7 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
|
|||||||
if (!src[FPSTR(S_FACTOR)].isNull()) {
|
if (!src[FPSTR(S_FACTOR)].isNull()) {
|
||||||
float value = src[FPSTR(S_FACTOR)].as<float>();
|
float value = src[FPSTR(S_FACTOR)].as<float>();
|
||||||
|
|
||||||
if (value > 0.09f && value <= 10.0f && fabsf(value - dst.factor) > 0.0001f) {
|
if (value > 0.09f && value <= 100.0f && fabsf(value - dst.factor) > 0.0001f) {
|
||||||
dst.factor = roundf(value, 3);
|
dst.factor = roundf(value, 3);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
"wait": "Please wait...",
|
"wait": "Please wait...",
|
||||||
"uploading": "Uploading...",
|
"uploading": "Uploading...",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
"error": "Error"
|
"error": "Error",
|
||||||
|
"send": "Send"
|
||||||
},
|
},
|
||||||
|
|
||||||
"index": {
|
"index": {
|
||||||
@@ -467,6 +468,31 @@
|
|||||||
"settingsFile": "Settings file",
|
"settingsFile": "Settings file",
|
||||||
"fw": "Firmware",
|
"fw": "Firmware",
|
||||||
"fs": "Filesystem"
|
"fs": "Filesystem"
|
||||||
|
},
|
||||||
|
|
||||||
|
"opentherm_request": {
|
||||||
|
"title": "Custom requests - OpenTherm Gateway",
|
||||||
|
"name": "Custom requests",
|
||||||
|
"section": {
|
||||||
|
"read": "Read",
|
||||||
|
"read.desc": "Send a read request with a custom message ID"
|
||||||
|
},
|
||||||
|
"messageId": "Message ID",
|
||||||
|
"result": {
|
||||||
|
"valid": "Valid",
|
||||||
|
"parityValid": "Parity valid",
|
||||||
|
"responseMessageIdValid": "Response message ID valid",
|
||||||
|
"responseType": "Response type",
|
||||||
|
"data": "Data value",
|
||||||
|
"flags": "As flags",
|
||||||
|
"hex": "As hex",
|
||||||
|
"fixedPoint": "As f8.8",
|
||||||
|
"u8": "As two u8",
|
||||||
|
"s8": "As two s8",
|
||||||
|
"u16": "As u16",
|
||||||
|
"s16": "As s16"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +146,7 @@
|
|||||||
<a href="/settings.html" role="button" data-i18n>settings.name</a>
|
<a href="/settings.html" role="button" data-i18n>settings.name</a>
|
||||||
<a href="/sensors.html" role="button" data-i18n>sensors.name</a>
|
<a href="/sensors.html" role="button" data-i18n>sensors.name</a>
|
||||||
<a href="/upgrade.html" role="button" data-i18n>upgrade.name</a>
|
<a href="/upgrade.html" role="button" data-i18n>upgrade.name</a>
|
||||||
|
<a href="/opentherm_request.html" role="button" data-i18n>opentherm_request.name</a>
|
||||||
<a href="/restart.html" role="button" class="secondary restart" data-i18n>button.restart</a>
|
<a href="/restart.html" role="button" class="secondary restart" data-i18n>button.restart</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
132
src_data/pages/opentherm_request.html
Normal file
132
src_data/pages/opentherm_request.html
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title data-i18n>opentherm_request.title</title>
|
||||||
|
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header class="container">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/">
|
||||||
|
<div class="logo" data-i18n>logo</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<select id="lang" aria-label="Lang">
|
||||||
|
<option value="en" selected>EN</option>
|
||||||
|
<option value="it">IT</option>
|
||||||
|
<option value="ru">RU</option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<article>
|
||||||
|
<div>
|
||||||
|
<hgroup>
|
||||||
|
<h2 data-i18n>opentherm_request.section.read</h2>
|
||||||
|
<p data-i18n>opentherm_request.section.read.desc</p>
|
||||||
|
</hgroup>
|
||||||
|
|
||||||
|
<form action="/api/opentherm_request/read" id="read">
|
||||||
|
<label for="message_id">
|
||||||
|
<span data-i18n>opentherm_request.messageId</span>
|
||||||
|
<input type="number" inputmode="numeric" name="messageId" id="message-id" min="0" max="255" step="1" required>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div role="group">
|
||||||
|
<button type="submit" data-i18n>button.send</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div role="group" id="read-result">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row" data-i18n>opentherm_request.result.valid</th>
|
||||||
|
<td><i class="mValid"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row" data-i18n>opentherm_request.result.parityValid</th>
|
||||||
|
<td><i class="mParityValid"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row" data-i18n>opentherm_request.result.responseMessageIdValid</th>
|
||||||
|
<td><i class="mResponseMessageIdValid"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row" data-i18n>opentherm_request.result.responseType</th>
|
||||||
|
<td><b class="mResponseType"></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="3" scope="row"><span data-i18n>opentherm_request.result.data</span>:</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row" data-i18n>opentherm_request.result.flags</th>
|
||||||
|
<td><b class="mDataFlagsHigh"></b></td>
|
||||||
|
<td><b class="mDataFlagsLow"></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row" data-i18n>opentherm_request.result.hex</th>
|
||||||
|
<td><b class="mDataHex"></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row" data-i18n>opentherm_request.result.fixedPoint</th>
|
||||||
|
<td><b class="mDataFixedPoint"></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row" data-i18n>opentherm_request.result.u8</th>
|
||||||
|
<td><b class="mDataU8High"></b></td>
|
||||||
|
<td><b class="mDataU8Low"></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row" data-i18n>opentherm_request.result.s8</th>
|
||||||
|
<td><b class="mDataS8High"></b></td>
|
||||||
|
<td><b class="mDataS8Low"></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row" data-i18n>opentherm_request.result.u16</th>
|
||||||
|
<td><b class="mDataU16"></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row" data-i18n>opentherm_request.result.s16</th>
|
||||||
|
<td><b class="mDataS16"></b></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="container">
|
||||||
|
<small>
|
||||||
|
<b>Made by Laxilef</b>
|
||||||
|
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary" data-i18n>nav.license</a>
|
||||||
|
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
|
||||||
|
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
|
||||||
|
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
|
||||||
|
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
|
||||||
|
</small>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="/static/app.js?{BUILD_TIME}"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
const lang = new Lang(document.getElementById('lang'));
|
||||||
|
lang.build();
|
||||||
|
|
||||||
|
setupOTReadForm('#read');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
|
|
||||||
<label>
|
<label>
|
||||||
<span data-i18n>sensors.correction.factor</span>
|
<span data-i18n>sensors.correction.factor</span>
|
||||||
<input type="number" inputmode="decimal" name="factor" min="0.01" max="10" step="0.01" required>
|
<input type="number" inputmode="decimal" name="factor" min="0.01" max="100" step="0.01" required>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -522,6 +522,107 @@ const setupUpgradeForm = (formSelector) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setupOTReadForm = (formSelector) => {
|
||||||
|
const form = document.querySelector(formSelector);
|
||||||
|
if (!form) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = form.action;
|
||||||
|
let button = form.querySelector('button[type="submit"]');
|
||||||
|
let defaultText;
|
||||||
|
|
||||||
|
hide("#read-result");
|
||||||
|
|
||||||
|
form.addEventListener('submit', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (button) {
|
||||||
|
defaultText = button.textContent;
|
||||||
|
button.textContent = i18n('button.wait');
|
||||||
|
button.setAttribute('disabled', true);
|
||||||
|
button.setAttribute('aria-busy', true);
|
||||||
|
}
|
||||||
|
hide("#read-result");
|
||||||
|
|
||||||
|
const onSuccess = (result) => {
|
||||||
|
if (button) {
|
||||||
|
button.textContent = i18n('button.success');
|
||||||
|
button.classList.add('success');
|
||||||
|
button.removeAttribute('aria-busy');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.removeAttribute('disabled');
|
||||||
|
button.classList.remove('success', 'failed');
|
||||||
|
button.textContent = defaultText;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
setState(".mValid", result.valid);
|
||||||
|
setState(".mParityValid", result.parityValid);
|
||||||
|
setState(".m", );
|
||||||
|
setState(".mResponseMessageIdValid", result.responseMessageIdValid);
|
||||||
|
setValue(".mResponseType", result.responseType);
|
||||||
|
|
||||||
|
const u16 = result.value;
|
||||||
|
const [flagsHigh, flagsLow] = u16ToFlags(u16);
|
||||||
|
const hex = u16ToHex(u16);
|
||||||
|
const fixedPoint = u16ToFixedPoint(u16);
|
||||||
|
const [u8High, u8Low] = u16ToU8s(u16);
|
||||||
|
const [s8High, s8Low] = u16ToS8s(u16);
|
||||||
|
const s16 = u16ToS16(u16);
|
||||||
|
|
||||||
|
setValue(".mDataFlagsHigh", flagsHigh);
|
||||||
|
setValue(".mDataFlagsLow", flagsLow);
|
||||||
|
setValue(".mDataHex", hex);
|
||||||
|
setValue(".mDataFixedPoint", fixedPoint);
|
||||||
|
setValue(".mDataU8High", u8High);
|
||||||
|
setValue(".mDataU8Low", u8Low);
|
||||||
|
setValue(".mDataS8High", s8High);
|
||||||
|
setValue(".mDataS8Low", s8Low);
|
||||||
|
setValue(".mDataU16", u16);
|
||||||
|
setValue(".mDataS16", s16);
|
||||||
|
|
||||||
|
show("#read-result");
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFailed = () => {
|
||||||
|
if (button) {
|
||||||
|
button.textContent = i18n('button.error');
|
||||||
|
button.classList.add('failed');
|
||||||
|
button.removeAttribute('aria-busy');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.removeAttribute('disabled');
|
||||||
|
button.classList.remove('success', 'failed');
|
||||||
|
button.textContent = defaultText;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const messageId = form.querySelector('#message-id').value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let fd = new FormData(form);
|
||||||
|
let response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
cache: "no-cache",
|
||||||
|
credentials: "include",
|
||||||
|
body: form2json(fd)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Response not valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
onSuccess(result);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
onFailed(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const setBusy = (busySelector, contentSelector, value, parent = undefined) => {
|
const setBusy = (busySelector, contentSelector, value, parent = undefined) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -848,4 +949,36 @@ function dec2hex(i) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return hex.toUpperCase();
|
return hex.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function u16ToHex(i) {
|
||||||
|
return (i >>> 0).toString(16).padStart(4, "0").toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function u16ToU8s(i) {
|
||||||
|
let low = (i >>> 0) & 0xFF;
|
||||||
|
let high = ((i >>> 0) & 0xFF00) >> 8;
|
||||||
|
return [high, low];
|
||||||
|
}
|
||||||
|
|
||||||
|
function u16ToS8s(i) {
|
||||||
|
let [high, low] = u16ToU8s(i);
|
||||||
|
return [high << 24 >> 24, low << 24 >> 24];
|
||||||
|
}
|
||||||
|
|
||||||
|
function u16ToS16(i) {
|
||||||
|
return (i >>> 0) << 16 >> 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
function u16ToFlags(i) {
|
||||||
|
let [high, low] = u16ToU8s(i);
|
||||||
|
return [
|
||||||
|
high.toString(2).padStart(8, "0"),
|
||||||
|
low.toString(2).padStart(8, "0")
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function u16ToFixedPoint(i) {
|
||||||
|
let [high, low] = u16ToU8s(i);
|
||||||
|
return (high + low / 256).toFixed(3)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user