mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-25 17:43:35 +05:00
Compare commits
10 Commits
995938f11a
...
arduino_fr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
842b443723 | ||
|
|
3bf65aeaad | ||
|
|
1b004b25c7 | ||
|
|
95b18385ba | ||
|
|
4457e16a8f | ||
|
|
1965ca671e | ||
|
|
0d1873ec77 | ||
|
|
38ec56fb33 | ||
|
|
bb7c3eeba3 | ||
|
|
0c778d4c7f |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,8 +1,13 @@
|
||||
.pio
|
||||
.vscode
|
||||
build/*.bin
|
||||
build/*
|
||||
data/*
|
||||
managed_components/*
|
||||
node_modules/*
|
||||
secrets.ini
|
||||
node_modules
|
||||
package-lock.json
|
||||
*.lock
|
||||
sdkconfig.*
|
||||
CMakeLists.txt
|
||||
!sdkconfig.defaults
|
||||
!.gitkeep
|
||||
@@ -48,6 +48,10 @@ let paths = {
|
||||
{
|
||||
src: 'src_data/images/*.*',
|
||||
dest: 'data/static/images/'
|
||||
},
|
||||
{
|
||||
src: 'src_data/*.txt',
|
||||
dest: 'data/static/'
|
||||
}
|
||||
],
|
||||
pages: {
|
||||
|
||||
@@ -84,7 +84,7 @@ board_build.ldscript = eagle.flash.4m1m.ld
|
||||
;platform_packages =
|
||||
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.5
|
||||
; framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.1/esp32-arduino-libs-idf-release_v5.1-33fbade6.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.12/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip
|
||||
platform_packages =
|
||||
board_build.partitions = esp32_partitions.csv
|
||||
lib_deps =
|
||||
@@ -287,21 +287,26 @@ build_flags =
|
||||
|
||||
[env:esp32_c6]
|
||||
platform = ${esp32_defaults.platform}
|
||||
framework = arduino, espidf
|
||||
platform_packages = ${esp32_defaults.platform_packages}
|
||||
board = esp32-c6-devkitm-1
|
||||
board_build.partitions = ${esp32_defaults.board_build.partitions}
|
||||
lib_deps =
|
||||
${esp32_defaults.lib_deps}
|
||||
;${esp32_defaults.nimble_lib}
|
||||
lib_ignore = ${esp32_defaults.lib_ignore}
|
||||
lib_deps = ${esp32_defaults.lib_deps}
|
||||
lib_ignore =
|
||||
${esp32_defaults.lib_ignore}
|
||||
extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_unflags =
|
||||
-mtext-section-literals
|
||||
build_type = ${esp32_defaults.build_type}
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
; Currently the NimBLE library is incompatible with ESP32 C6
|
||||
;-D USE_BLE=1
|
||||
-D USE_BLE=1
|
||||
-D DEFAULT_OT_IN_GPIO=15
|
||||
-D DEFAULT_OT_OUT_GPIO=23
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=0
|
||||
-D DEFAULT_STATUS_LED_GPIO=11
|
||||
-D DEFAULT_OT_RX_LED_GPIO=10
|
||||
|
||||
[env:otthing]
|
||||
platform = ${esp32_defaults.platform}
|
||||
|
||||
33
sdkconfig.defaults
Normal file
33
sdkconfig.defaults
Normal file
@@ -0,0 +1,33 @@
|
||||
# Source:
|
||||
# https://github.com/pioarduino/platform-espressif32/tree/main/examples/espidf-arduino-h2zero-BLE_scan
|
||||
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_MBEDTLS_PSK_MODES=y
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y
|
||||
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
|
||||
|
||||
#
|
||||
# BT config
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
|
||||
#
|
||||
# Arduino Configuration
|
||||
CONFIG_AUTOSTART_ARDUINO=y
|
||||
CONFIG_ARDUINO_SELECTIVE_COMPILATION=y
|
||||
CONFIG_ARDUINO_SELECTIVE_Zigbee=n
|
||||
CONFIG_ARDUINO_SELECTIVE_Matter=n
|
||||
CONFIG_ARDUINO_SELECTIVE_WiFiProv=n
|
||||
CONFIG_ARDUINO_SELECTIVE_BLE=n
|
||||
CONFIG_ARDUINO_SELECTIVE_BluetoothSerial=n
|
||||
CONFIG_ARDUINO_SELECTIVE_SimpleBLE=n
|
||||
CONFIG_ARDUINO_SELECTIVE_RainMaker=n
|
||||
CONFIG_ARDUINO_SELECTIVE_OpenThread=n
|
||||
CONFIG_ARDUINO_SELECTIVE_Insights=n
|
||||
@@ -70,8 +70,8 @@ public:
|
||||
this->prevPubVarsTime = 0;
|
||||
}
|
||||
|
||||
inline void rebuildHaEntity(uint8_t sensorId, Sensors::Settings& prevSettings) {
|
||||
this->queueRebuildingHaEntities[sensorId] = prevSettings;
|
||||
inline void reconfigureSensor(uint8_t sensorId, Sensors::Settings& prevSettings) {
|
||||
this->queueReconfigureSensors[sensorId] = prevSettings;
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -81,7 +81,7 @@ protected:
|
||||
MqttWriter* writer = nullptr;
|
||||
UnitSystem currentUnitSystem = UnitSystem::METRIC;
|
||||
bool currentHomeAssistantDiscovery = false;
|
||||
std::unordered_map<uint8_t, Sensors::Settings> queueRebuildingHaEntities;
|
||||
std::unordered_map<uint8_t, Sensors::Settings> queueReconfigureSensors;
|
||||
unsigned short readyForSendTime = 30000;
|
||||
unsigned long lastReconnectTime = 0;
|
||||
unsigned long connectedTime = 0;
|
||||
@@ -276,8 +276,8 @@ protected:
|
||||
this->publishNonStaticHaEntities();
|
||||
}
|
||||
|
||||
|
||||
for (auto& [sensorId, prevSettings] : this->queueRebuildingHaEntities) {
|
||||
// rebuilding ha configs
|
||||
for (auto& [sensorId, prevSettings] : this->queueReconfigureSensors) {
|
||||
Log.sinfoln(FPSTR(L_MQTT_HA), F("Rebuilding config for sensor #%hhu '%s'"), sensorId, prevSettings.name);
|
||||
|
||||
// delete old config
|
||||
@@ -297,15 +297,6 @@ protected:
|
||||
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
|
||||
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE);
|
||||
break;
|
||||
|
||||
case Sensors::Type::MANUAL:
|
||||
this->client->unsubscribe(
|
||||
this->haHelper->getDeviceTopic(
|
||||
F("sensors"),
|
||||
Sensors::makeObjectId(prevSettings.name).c_str(),
|
||||
F("set")
|
||||
).c_str()
|
||||
);
|
||||
|
||||
default:
|
||||
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::PRIMARY);
|
||||
@@ -333,26 +324,51 @@ protected:
|
||||
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
|
||||
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
|
||||
break;
|
||||
|
||||
case Sensors::Type::MANUAL:
|
||||
this->client->subscribe(
|
||||
this->haHelper->getDeviceTopic(
|
||||
F("sensors"),
|
||||
Sensors::makeObjectId(prevSettings.name).c_str(),
|
||||
F("set")
|
||||
).c_str()
|
||||
);
|
||||
|
||||
default:
|
||||
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem);
|
||||
}
|
||||
}
|
||||
this->queueRebuildingHaEntities.clear();
|
||||
|
||||
} else if (this->currentHomeAssistantDiscovery) {
|
||||
this->currentHomeAssistantDiscovery = false;
|
||||
}
|
||||
|
||||
// reconfigure manual sensors
|
||||
for (auto& [sensorId, prevSettings] : this->queueReconfigureSensors) {
|
||||
// unsubscribe from old topic
|
||||
if (strlen(prevSettings.name) && prevSettings.enabled) {
|
||||
if (prevSettings.type == Sensors::Type::MANUAL) {
|
||||
this->client->unsubscribe(
|
||||
this->haHelper->getDeviceTopic(
|
||||
F("sensors"),
|
||||
Sensors::makeObjectId(prevSettings.name).c_str(),
|
||||
F("set")
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Sensors::hasEnabledAndValid(sensorId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// subscribe to new topic
|
||||
auto& sSettings = Sensors::settings[sensorId];
|
||||
if (sSettings.type == Sensors::Type::MANUAL) {
|
||||
this->client->subscribe(
|
||||
this->haHelper->getDeviceTopic(
|
||||
F("sensors"),
|
||||
Sensors::makeObjectId(sSettings.name).c_str(),
|
||||
F("set")
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// clear queue
|
||||
this->queueReconfigureSensors.clear();
|
||||
|
||||
if (this->newConnection) {
|
||||
this->newConnection = false;
|
||||
}
|
||||
@@ -366,6 +382,26 @@ protected:
|
||||
|
||||
this->client->subscribe(this->haHelper->getDeviceTopic(F("settings/set")).c_str());
|
||||
this->client->subscribe(this->haHelper->getDeviceTopic(F("state/set")).c_str());
|
||||
|
||||
// subscribe to manual sensors
|
||||
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
|
||||
if (!Sensors::hasEnabledAndValid(sensorId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& sSettings = Sensors::settings[sensorId];
|
||||
if (sSettings.type != Sensors::Type::MANUAL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this->client->subscribe(
|
||||
this->haHelper->getDeviceTopic(
|
||||
F("sensors"),
|
||||
Sensors::makeObjectId(sSettings.name).c_str(),
|
||||
F("set")
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void onDisconnect() {
|
||||
@@ -513,15 +549,6 @@ protected:
|
||||
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
|
||||
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
|
||||
break;
|
||||
|
||||
case Sensors::Type::MANUAL:
|
||||
this->client->subscribe(
|
||||
this->haHelper->getDeviceTopic(
|
||||
F("sensors"),
|
||||
Sensors::makeObjectId(sSettings.name).c_str(),
|
||||
F("set")
|
||||
).c_str()
|
||||
);
|
||||
|
||||
default:
|
||||
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem);
|
||||
|
||||
@@ -9,30 +9,6 @@ public:
|
||||
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:
|
||||
const unsigned short readyTime = 60000u;
|
||||
const unsigned int resetBusInterval = 120000u;
|
||||
@@ -238,7 +214,12 @@ protected:
|
||||
);
|
||||
}
|
||||
|
||||
if (!vars.slave.connected && millis() - this->lastSuccessResponse < 1325) {
|
||||
// 5 request retries
|
||||
// 1000ms maximum response waiting time
|
||||
// 100ms delay between requests
|
||||
// +15%
|
||||
// 5 * (1000 + 100) * 1.15 = 6325 ms
|
||||
if (!vars.slave.connected && millis() - this->lastSuccessResponse < 6325) {
|
||||
Log.sinfoln(
|
||||
FPSTR(L_OT),
|
||||
F("Connected, downtime: %lu s."),
|
||||
@@ -248,7 +229,7 @@ protected:
|
||||
this->connectedTime = millis();
|
||||
vars.slave.connected = true;
|
||||
|
||||
} else if (vars.slave.connected && millis() - this->lastSuccessResponse > 1325) {
|
||||
} else if (vars.slave.connected && millis() - this->lastSuccessResponse > 6325) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_OT),
|
||||
F("Disconnected, uptime: %lu s."),
|
||||
|
||||
@@ -21,7 +21,6 @@ using namespace NetworkUtils;
|
||||
extern NetworkMgr* network;
|
||||
extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
|
||||
extern MqttTask* tMqtt;
|
||||
extern OpenThermTask* tOt;
|
||||
|
||||
|
||||
class PortalTask : public LeanTask {
|
||||
@@ -56,6 +55,7 @@ protected:
|
||||
bool webServerEnabled = false;
|
||||
bool dnsServerEnabled = false;
|
||||
unsigned long webServerChangeState = 0;
|
||||
bool mDnsState = false;
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
const char* getTaskName() override {
|
||||
@@ -179,18 +179,6 @@ protected:
|
||||
});
|
||||
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
|
||||
auto upgradeHandler = (new UpgradeHandler("/api/upgrade"))->setCanUploadCallback([this](const String& uri) {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
@@ -630,7 +618,7 @@ protected:
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
tMqtt->rebuildHaEntity(sensorId, prevSettings);
|
||||
tMqtt->reconfigureSensor(sensorId, prevSettings);
|
||||
fsSensorsSettings.update();
|
||||
}
|
||||
});
|
||||
@@ -855,41 +843,6 @@ protected:
|
||||
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
|
||||
this->webServer->onNotFound([this]() {
|
||||
@@ -907,6 +860,7 @@ protected:
|
||||
}
|
||||
});
|
||||
|
||||
this->webServer->serveStatic("/robots.txt", LittleFS, "/static/robots.txt", PORTAL_CACHE);
|
||||
this->webServer->serveStatic("/favicon.ico", LittleFS, "/static/images/favicon.ico", PORTAL_CACHE);
|
||||
this->webServer->serveStatic("/static", LittleFS, "/static", PORTAL_CACHE);
|
||||
}
|
||||
@@ -921,8 +875,14 @@ protected:
|
||||
this->startWebServer();
|
||||
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Started: AP up or STA connected"));
|
||||
|
||||
if (MDNS.begin(networkSettings.hostname)) {
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
// Enabling mDNS
|
||||
if (!this->mDnsState && settings.portal.mdns) {
|
||||
if (MDNS.begin(networkSettings.hostname)) {
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
this->mDnsState = true;
|
||||
|
||||
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("mDNS enabled and service added"));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
@@ -933,13 +893,27 @@ protected:
|
||||
this->stopWebServer();
|
||||
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Stopped: AP and STA down"));
|
||||
|
||||
MDNS.end();
|
||||
// Disabling mDNS
|
||||
if (this->mDnsState) {
|
||||
MDNS.end();
|
||||
this->mDnsState = false;
|
||||
|
||||
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("mDNS disabled"));
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Disabling mDNS if disabled in settings
|
||||
if (this->mDnsState && !settings.portal.mdns) {
|
||||
MDNS.end();
|
||||
this->mDnsState = false;
|
||||
|
||||
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("mDNS disabled"));
|
||||
}
|
||||
|
||||
// dns server
|
||||
if (!this->stateDnsServer() && !network->isConnected() && network->isApEnabled() && this->stateWebServer()) {
|
||||
this->startDnsServer();
|
||||
|
||||
@@ -49,6 +49,7 @@ struct Settings {
|
||||
bool auth = false;
|
||||
char login[13] = DEFAULT_PORTAL_LOGIN;
|
||||
char password[33] = DEFAULT_PORTAL_PASSWORD;
|
||||
bool mdns = true;
|
||||
} portal;
|
||||
|
||||
struct {
|
||||
|
||||
3
src/idf_component.yml
Normal file
3
src/idf_component.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
idf: ">=5.3.2"
|
||||
h2zero/esp-nimble-cpp: ">=2.2.1"
|
||||
@@ -122,8 +122,8 @@ const char S_MAX_MODULATION[] PROGMEM = "maxModulation";
|
||||
const char S_MAX_POWER[] PROGMEM = "maxPower";
|
||||
const char S_MAX_TEMP[] PROGMEM = "maxTemp";
|
||||
const char S_MAX_TEMP_SYNC_WITH_TARGET_TEMP[] PROGMEM = "maxTempSyncWithTargetTemp";
|
||||
const char S_MDNS[] PROGMEM = "mdns";
|
||||
const char S_MEMBER_ID[] PROGMEM = "memberId";
|
||||
const char S_MESSAGE_ID[] PROGMEM = "messageId";
|
||||
const char S_MIN[] PROGMEM = "min";
|
||||
const char S_MIN_FREE[] PROGMEM = "minFree";
|
||||
const char S_MIN_MAX_FREE_BLOCK[] PROGMEM = "minMaxFreeBlock";
|
||||
@@ -148,7 +148,6 @@ const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
|
||||
const char S_OUT_GPIO[] PROGMEM = "outGpio";
|
||||
const char S_OUTPUT[] PROGMEM = "output";
|
||||
const char S_PASSWORD[] PROGMEM = "password";
|
||||
const char S_PARITY_VALID[] PROGMEM = "parityValid";
|
||||
const char S_PID[] PROGMEM = "pid";
|
||||
const char S_PORT[] PROGMEM = "port";
|
||||
const char S_PORTAL[] PROGMEM = "portal";
|
||||
@@ -165,8 +164,6 @@ const char S_RESET_DIAGNOSTIC[] PROGMEM = "resetDiagnostic";
|
||||
const char S_RESET_FAULT[] PROGMEM = "resetFault";
|
||||
const char S_RESET_REASON[] PROGMEM = "resetReason";
|
||||
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_REV[] PROGMEM = "rev";
|
||||
const char S_RSSI[] PROGMEM = "rssi";
|
||||
@@ -207,6 +204,5 @@ const char S_UPTIME[] PROGMEM = "uptime";
|
||||
const char S_USE[] PROGMEM = "use";
|
||||
const char S_USE_DHCP[] PROGMEM = "useDhcp";
|
||||
const char S_USER[] PROGMEM = "user";
|
||||
const char S_VALID[] PROGMEM = "valid";
|
||||
const char S_VALUE[] PROGMEM = "value";
|
||||
const char S_VERSION[] PROGMEM = "version";
|
||||
|
||||
10
src/utils.h
10
src/utils.h
@@ -440,6 +440,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
portal[FPSTR(S_AUTH)] = src.portal.auth;
|
||||
portal[FPSTR(S_LOGIN)] = src.portal.login;
|
||||
portal[FPSTR(S_PASSWORD)] = src.portal.password;
|
||||
portal[FPSTR(S_MDNS)] = src.portal.mdns;
|
||||
|
||||
auto opentherm = dst[FPSTR(S_OPENTHERM)].to<JsonObject>();
|
||||
opentherm[FPSTR(S_UNIT_SYSTEM)] = static_cast<uint8_t>(src.opentherm.unitSystem);
|
||||
@@ -707,6 +708,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (src[FPSTR(S_PORTAL)][FPSTR(S_MDNS)].is<bool>()) {
|
||||
bool value = src[FPSTR(S_PORTAL)][FPSTR(S_MDNS)].as<bool>();
|
||||
|
||||
if (value != dst.portal.mdns) {
|
||||
dst.portal.mdns = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// opentherm
|
||||
if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_UNIT_SYSTEM)].isNull()) {
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
"wait": "Please wait...",
|
||||
"uploading": "Uploading...",
|
||||
"success": "Success",
|
||||
"error": "Error",
|
||||
"send": "Send"
|
||||
"error": "Error"
|
||||
},
|
||||
|
||||
"index": {
|
||||
@@ -297,7 +296,8 @@
|
||||
"portal": {
|
||||
"login": "Login",
|
||||
"password": "Password",
|
||||
"auth": "Require authentication"
|
||||
"auth": "Require authentication",
|
||||
"mdns": "Use mDNS"
|
||||
},
|
||||
|
||||
"system": {
|
||||
@@ -468,31 +468,6 @@
|
||||
"settingsFile": "Settings file",
|
||||
"fw": "Firmware",
|
||||
"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"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,7 @@
|
||||
"wait": "Attendi...",
|
||||
"uploading": "caricamento...",
|
||||
"success": "Riuscito",
|
||||
"error": "Errore",
|
||||
"send": "Invia"
|
||||
"error": "Errore"
|
||||
},
|
||||
|
||||
"index": {
|
||||
@@ -297,7 +296,8 @@
|
||||
"portal": {
|
||||
"login": "Login",
|
||||
"password": "Password",
|
||||
"auth": "Richiede autenticazione"
|
||||
"auth": "Richiede autenticazione",
|
||||
"mdns": "Usa mDNS"
|
||||
},
|
||||
|
||||
"system": {
|
||||
@@ -468,30 +468,6 @@
|
||||
"settingsFile": "Settings file",
|
||||
"fw": "Firmware",
|
||||
"fs": "Filesystem"
|
||||
},
|
||||
|
||||
"opentherm_request": {
|
||||
"title": "Richiesta personalizzata - OpenTherm Gateway",
|
||||
"name": "Richiesta personalizzata",
|
||||
"section": {
|
||||
"read": "Lettura",
|
||||
"read.desc": "Invia una lettura con un ID messaggio personalizzato"
|
||||
},
|
||||
"messageId": "ID messaggio",
|
||||
"result": {
|
||||
"valid": "Valido",
|
||||
"parityValid": "Parità valida",
|
||||
"responseMessageIdValid": "ID messaggio di risposta valido",
|
||||
"responseType": "Tipo risposta",
|
||||
"data": "Valore",
|
||||
"flags": "Formato flags",
|
||||
"hex": "Formato esadecimale",
|
||||
"fixedPoint": "Formato f8.8",
|
||||
"u8": "Formato due u8",
|
||||
"s8": "Formato due s8",
|
||||
"u16": "Formato u16",
|
||||
"s16": "Formato s16"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,7 @@
|
||||
"wait": "Пожалуйста, подождите...",
|
||||
"uploading": "Загрузка...",
|
||||
"success": "Успешно",
|
||||
"error": "Ошибка",
|
||||
"send": "Отправить"
|
||||
"error": "Ошибка"
|
||||
},
|
||||
|
||||
"index": {
|
||||
@@ -297,7 +296,8 @@
|
||||
"portal": {
|
||||
"login": "Логин",
|
||||
"password": "Пароль",
|
||||
"auth": "Требовать аутентификацию"
|
||||
"auth": "Требовать аутентификацию",
|
||||
"mdns": "Использовать mDNS"
|
||||
},
|
||||
|
||||
"system": {
|
||||
@@ -468,31 +468,6 @@
|
||||
"settingsFile": "Файл настроек",
|
||||
"fw": "Прошивка",
|
||||
"fs": "Файловая система"
|
||||
},
|
||||
|
||||
"opentherm_request": {
|
||||
"title": "Специальные запросы - OpenTherm Gateway",
|
||||
"name": "Специальные запросы",
|
||||
"section": {
|
||||
"read": "Чтение",
|
||||
"read.desc": "В этом разделе вы можете отправить запрос на чтение с произвольным message ID."
|
||||
},
|
||||
"messageId": "Message ID",
|
||||
"result": {
|
||||
"valid": "Ответ верен",
|
||||
"parityValid": "Чётность верна",
|
||||
"responseMessageIdValid": "Message ID ответа верно",
|
||||
"responseType": "Тип ответа",
|
||||
"data": "Данные",
|
||||
"flags": "В виде флагов",
|
||||
"hex": "В шестнадцатеричном виде",
|
||||
"fixedPoint": "В виде f8.8",
|
||||
"u8": "В виде двух u8",
|
||||
"s8": "В виде двух s8",
|
||||
"u16": "В виде u16",
|
||||
"s16": "В виде s16"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,6 @@
|
||||
<a href="/settings.html" role="button" data-i18n>settings.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="/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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
<!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>
|
||||
@@ -57,6 +57,11 @@
|
||||
<input type="checkbox" name="portal[auth]" value="true">
|
||||
<span data-i18n>settings.portal.auth</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" name="portal[mdns]" value="true">
|
||||
<span data-i18n>settings.portal.mdns</span>
|
||||
</label>
|
||||
<br />
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
@@ -773,6 +778,7 @@
|
||||
setCheckboxValue("[name='portal[auth]']", data.portal.auth);
|
||||
setInputValue("[name='portal[login]']", data.portal.login);
|
||||
setInputValue("[name='portal[password]']", data.portal.password);
|
||||
setCheckboxValue("[name='portal[mdns]']", data.portal.mdns);
|
||||
setBusy('#portal-settings-busy', '#portal-settings', false);
|
||||
|
||||
// Opentherm
|
||||
|
||||
2
src_data/robots.txt
Normal file
2
src_data/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
@@ -132,10 +132,14 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
let row = tbody.insertRow(-1);
|
||||
row.classList.add("network");
|
||||
row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid);
|
||||
row.onclick = () => {
|
||||
const input = document.querySelector('input#sta-ssid');
|
||||
const ssid = this.getAttribute('data-ssid');
|
||||
row.dataset.ssid = result[i].hidden ? '' : result[i].ssid;
|
||||
row.insertCell().textContent = `#${i + 1}`;
|
||||
|
||||
const nameCell = row.insertCell();
|
||||
nameCell.innerHTML = result[i].hidden ? `<i>${result[i].bssid}</i>` : result[i].ssid;
|
||||
nameCell.onclick = (event) => {
|
||||
const input = document.querySelector("[name='sta[ssid]']");
|
||||
const ssid = event.target.parentNode.dataset.ssid;
|
||||
if (!input || !ssid) {
|
||||
return;
|
||||
}
|
||||
@@ -144,9 +148,6 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
|
||||
input.focus();
|
||||
};
|
||||
|
||||
row.insertCell().textContent = `#${i + 1}`;
|
||||
row.insertCell().innerHTML = result[i].hidden ? `<i>${result[i].bssid}</i>` : result[i].ssid;
|
||||
|
||||
// info cell
|
||||
let infoCell = row.insertCell();
|
||||
|
||||
@@ -165,7 +166,7 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
|
||||
}
|
||||
|
||||
let signalQualityContainer = document.createElement("span");
|
||||
signalQualityContainer.setAttribute('data-tooltip', `${result[i].signalQuality}%`);
|
||||
signalQualityContainer.dataset.tooltip = `${result[i].signalQuality}%`;
|
||||
signalQualityContainer.appendChild(signalQualityIcon);
|
||||
infoCell.appendChild(signalQualityContainer);
|
||||
|
||||
@@ -192,7 +193,7 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
|
||||
}
|
||||
|
||||
let authContainer = document.createElement("span");
|
||||
authContainer.setAttribute('data-tooltip', (result[i].auth in authList) ? authList[result[i].auth] : "unknown");
|
||||
authContainer.dataset.tooltip = (result[i].auth in authList) ? authList[result[i].auth] : "unknown";
|
||||
authContainer.appendChild(authIcon);
|
||||
infoCell.appendChild(authContainer);
|
||||
}
|
||||
@@ -522,107 +523,6 @@ 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) => {
|
||||
if (!value) {
|
||||
@@ -949,36 +849,4 @@ function dec2hex(i) {
|
||||
}
|
||||
|
||||
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