diff --git a/include/bonjour/DiscoveryRecord.h b/include/bonjour/DiscoveryRecord.h index 333030320..6c22462c9 100644 --- a/include/bonjour/DiscoveryRecord.h +++ b/include/bonjour/DiscoveryRecord.h @@ -35,7 +35,7 @@ class DiscoveryRecord { public: - enum Service { Unknown = 0, HyperHDR, WLED, PhilipsHue, Pico, ESP32_S2, ESP, SerialPort, REFRESH_ALL }; + enum Service { Unknown = 0, HyperHDR, WLED, PhilipsHue, HomeAssistant, Pico, ESP32_S2, ESP, SerialPort, REFRESH_ALL }; Service type; QString hostName; diff --git a/include/bonjour/DiscoveryWrapper.h b/include/bonjour/DiscoveryWrapper.h index f96e3e822..0e33aadbe 100644 --- a/include/bonjour/DiscoveryWrapper.h +++ b/include/bonjour/DiscoveryWrapper.h @@ -50,6 +50,7 @@ class DiscoveryWrapper : public QObject public slots: QList getPhilipsHUE(); + QList getHomeAssistant(); QList getWLED(); QList getHyperHDRServices(); QList getAllServices(); @@ -66,5 +67,5 @@ public slots: void cleanUp(QList& target); // contains all current active service sessions - QList _hyperhdrSessions, _wledDevices, _hueDevices, _espDevices, _picoDevices, _esp32s2Devices; + QList _hyperhdrSessions, _wledDevices, _hueDevices, _homeAssistantDevices, _espDevices, _picoDevices, _esp32s2Devices; }; diff --git a/include/led-drivers/net/DriverNetHomeAssistant.h b/include/led-drivers/net/DriverNetHomeAssistant.h new file mode 100644 index 000000000..e95f7b16e --- /dev/null +++ b/include/led-drivers/net/DriverNetHomeAssistant.h @@ -0,0 +1,68 @@ +#pragma once + +#ifndef PCH_ENABLED + #include + #include + #include + #include +#endif + +#include +#include +#include + +class DriverNetHomeAssistant : public LedDevice +{ + Q_OBJECT + + struct HomeAssistantLamp; + + struct HomeAssistantInstance + { + QString homeAssistantHost; + QString longLivedAccessToken; + int transition; + int constantBrightness; + bool restoreOriginalState; + + std::list lamps; + }; + + struct HomeAssistantLamp + { + enum Mode { RGB = 0, HSV }; + + QString name; + Mode colorModel; + + struct + { + int isPoweredOn = -1; + int brightness = -1; + QJsonArray color; + } orgState; + }; + +public: + explicit DriverNetHomeAssistant(const QJsonObject& deviceConfig); + static LedDevice* construct(const QJsonObject& deviceConfig); + + QJsonObject discover(const QJsonObject& params) override; + +protected: + bool powerOn() override; + bool powerOff() override; + +private: + bool init(const QJsonObject& deviceConfig) override; + int write(const std::vector& ledValues) override; + bool powerOnOff(bool isOn); + bool saveStates(); + void restoreStates(); + + HomeAssistantInstance _haInstance; + + std::unique_ptr _restApi; + + static bool isRegistered; +}; diff --git a/sources/api/HyperAPI.cpp b/sources/api/HyperAPI.cpp index 2fad75242..b27dd3a69 100644 --- a/sources/api/HyperAPI.cpp +++ b/sources/api/HyperAPI.cpp @@ -1294,7 +1294,7 @@ void HyperAPI::handleTunnel(const QJsonObject& message, const QString& command, const QString& data = message["data"].toString().trimmed(); const QString& service = message["service"].toString().trimmed(); - if (service == "hue") + if (service == "hue" || service == "home_assistant") { QUrl tempUrl("http://"+ip); if ((path.indexOf("/clip/v2") != 0 && path.indexOf("/api") != 0) || ip.indexOf("/") >= 0) @@ -1305,7 +1305,7 @@ void HyperAPI::handleTunnel(const QJsonObject& message, const QString& command, ProviderRestApi provider; - QUrl url = QUrl((path.startsWith("/clip/v2") ? "https://" : "http://")+tempUrl.host()+path); + QUrl url = QUrl((path.startsWith("/clip/v2") ? "https://" : "http://")+tempUrl.host() + ((service == "home_assistant" && tempUrl.port() >= 0) ? ":" + QString::number(tempUrl.port()) : "") + path); Debug(_log, "Tunnel request for: %s", QSTRING_CSTR(url.toString())); diff --git a/sources/api/JSONRPC_schema/schema-tunnel.json b/sources/api/JSONRPC_schema/schema-tunnel.json index 78560e77c..b879acbe6 100644 --- a/sources/api/JSONRPC_schema/schema-tunnel.json +++ b/sources/api/JSONRPC_schema/schema-tunnel.json @@ -18,7 +18,7 @@ "service": { "type" : "string", "required" : true, - "enum" : ["hue"] + "enum" : ["hue", "home_assistant"] }, "ip": { "type" : "string", diff --git a/sources/bonjour/BonjourServiceHelper.cpp b/sources/bonjour/BonjourServiceHelper.cpp index 485f67c94..e0ddcac36 100644 --- a/sources/bonjour/BonjourServiceHelper.cpp +++ b/sources/bonjour/BonjourServiceHelper.cpp @@ -38,7 +38,8 @@ BonjourServiceHelper::BonjourServiceHelper(BonjourServiceRegister* parent, QStri has_ipv6 = 0; _scanService = (1 << DiscoveryRecord::Service::HyperHDR) | (1 << DiscoveryRecord::Service::WLED) | - (1 << DiscoveryRecord::Service::PhilipsHue); + (1 << DiscoveryRecord::Service::PhilipsHue) | + (1 << DiscoveryRecord::Service::HomeAssistant); _running = true; @@ -506,7 +507,8 @@ int BonjourServiceHelper::service_mdns(QString hostname, QString serviceName, in { for (auto scanner :{ DiscoveryRecord::Service::HyperHDR, DiscoveryRecord::Service::WLED, - DiscoveryRecord::Service::PhilipsHue }) + DiscoveryRecord::Service::PhilipsHue, + DiscoveryRecord::Service::HomeAssistant }) { if (_scanService & (1 << scanner)) { diff --git a/sources/bonjour/BonjourServiceRegister.cpp b/sources/bonjour/BonjourServiceRegister.cpp index 7a3336795..6db762edd 100644 --- a/sources/bonjour/BonjourServiceRegister.cpp +++ b/sources/bonjour/BonjourServiceRegister.cpp @@ -89,6 +89,7 @@ void BonjourServiceRegister::requestToScanHandler(DiscoveryRecord::Service type) case (DiscoveryRecord::Service::HyperHDR): _helper->_scanService |= (1 << DiscoveryRecord::Service::HyperHDR); break; case (DiscoveryRecord::Service::WLED): _helper->_scanService |= (1 << DiscoveryRecord::Service::WLED); break; case (DiscoveryRecord::Service::PhilipsHue): _helper->_scanService |= (1 << DiscoveryRecord::Service::PhilipsHue); break; + case (DiscoveryRecord::Service::HomeAssistant): _helper->_scanService |= (1 << DiscoveryRecord::Service::HomeAssistant); break; default: break; } } @@ -102,6 +103,8 @@ void BonjourServiceRegister::messageFromFriendHandler(bool isExists, QString mdn type = DiscoveryRecord::Service::WLED; else if (mdnsString.indexOf(DiscoveryRecord::getmDnsHeader(DiscoveryRecord::Service::PhilipsHue)) >= 0) type = DiscoveryRecord::Service::PhilipsHue; + else if (mdnsString.indexOf(DiscoveryRecord::getmDnsHeader(DiscoveryRecord::Service::HomeAssistant)) >= 0) + type = DiscoveryRecord::Service::HomeAssistant; else if (mdnsString.indexOf(DiscoveryRecord::getmDnsHeader(DiscoveryRecord::Service::HyperHDR)) >= 0) type = DiscoveryRecord::Service::HyperHDR; diff --git a/sources/bonjour/DiscoveryRecord.cpp b/sources/bonjour/DiscoveryRecord.cpp index cd9d56cd8..a447d72db 100644 --- a/sources/bonjour/DiscoveryRecord.cpp +++ b/sources/bonjour/DiscoveryRecord.cpp @@ -33,6 +33,7 @@ const QString DiscoveryRecord::getmDnsHeader(Service service) switch (service) { case(Service::PhilipsHue): return QLatin1String("_hue._tcp"); break; + case(Service::HomeAssistant): return QLatin1String("_home-assistant._tcp"); break; case(Service::WLED): return QLatin1String("_wled._tcp"); break; case(Service::HyperHDR): return QLatin1String("_hyperhdr-http._tcp"); break; default: return "SERVICE_UNKNOWN"; @@ -49,6 +50,7 @@ const QString DiscoveryRecord::getName(Service _type) switch (_type) { case(Service::PhilipsHue): return "Hue bridge"; break; + case(Service::HomeAssistant): return "Home Assistant"; break; case(Service::WLED): return "WLED"; break; case(Service::HyperHDR): return "HyperHDR"; break; case(Service::Pico): return "Pico/RP2040"; break; diff --git a/sources/bonjour/DiscoveryWrapper.cpp b/sources/bonjour/DiscoveryWrapper.cpp index 24282c496..250147085 100644 --- a/sources/bonjour/DiscoveryWrapper.cpp +++ b/sources/bonjour/DiscoveryWrapper.cpp @@ -88,6 +88,15 @@ QList DiscoveryWrapper::getPhilipsHUE() return _hueDevices; } +QList DiscoveryWrapper::getHomeAssistant() +{ + cleanUp(_homeAssistantDevices); + + emit GlobalSignals::getInstance()->SignalDiscoveryRequestToScan(DiscoveryRecord::Service::HomeAssistant); + + return _homeAssistantDevices; +} + QList DiscoveryWrapper::getWLED() { cleanUp(_wledDevices); @@ -104,7 +113,7 @@ QList DiscoveryWrapper::getHyperHDRServices() QList DiscoveryWrapper::getAllServices() { - return _hyperhdrSessions + _esp32s2Devices + _espDevices + _hueDevices + _picoDevices + _wledDevices; + return _hyperhdrSessions + _esp32s2Devices + _espDevices + _hueDevices + _homeAssistantDevices + _picoDevices + _wledDevices; } void DiscoveryWrapper::requestServicesScan() @@ -113,6 +122,8 @@ void DiscoveryWrapper::requestServicesScan() emit GlobalSignals::getInstance()->SignalDiscoveryRequestToScan(DiscoveryRecord::Service::WLED); cleanUp(_hueDevices); emit GlobalSignals::getInstance()->SignalDiscoveryRequestToScan(DiscoveryRecord::Service::PhilipsHue); + cleanUp(_homeAssistantDevices); + emit GlobalSignals::getInstance()->SignalDiscoveryRequestToScan(DiscoveryRecord::Service::HomeAssistant); cleanUp(_hyperhdrSessions); emit GlobalSignals::getInstance()->SignalDiscoveryRequestToScan(DiscoveryRecord::Service::HyperHDR); @@ -172,6 +183,8 @@ void DiscoveryWrapper::signalDiscoveryEventHandler(DiscoveryRecord message) gotMessage(_wledDevices, message); else if (message.type == DiscoveryRecord::Service::PhilipsHue) gotMessage(_hueDevices, message); + else if (message.type == DiscoveryRecord::Service::HomeAssistant) + gotMessage(_homeAssistantDevices, message); else if (message.type == DiscoveryRecord::Service::Pico) gotMessage(_picoDevices, message); else if (message.type == DiscoveryRecord::Service::ESP32_S2) diff --git a/sources/led-drivers/CMakeLists.txt b/sources/led-drivers/CMakeLists.txt index 788cc6576..66012fcb8 100644 --- a/sources/led-drivers/CMakeLists.txt +++ b/sources/led-drivers/CMakeLists.txt @@ -53,6 +53,7 @@ add_library(led-drivers OBJECT ${Leddevice_SOURCES} ) target_link_libraries(led-drivers Qt${Qt_VERSION}::Core Qt${Qt_VERSION}::Network + linalg ) IF ( HAVE_SERIAL_LED ) diff --git a/sources/led-drivers/LedDeviceSchemas.qrc b/sources/led-drivers/LedDeviceSchemas.qrc index 7117020d5..9b7fd2dc8 100644 --- a/sources/led-drivers/LedDeviceSchemas.qrc +++ b/sources/led-drivers/LedDeviceSchemas.qrc @@ -38,5 +38,6 @@ schemas/schema-yeelight.json schemas/schema-cololight.json schemas/schema-hyperspi.json + schemas/schema-home_assistant.json diff --git a/sources/led-drivers/net/DriverNetHomeAssistant.cpp b/sources/led-drivers/net/DriverNetHomeAssistant.cpp new file mode 100644 index 000000000..01e22c658 --- /dev/null +++ b/sources/led-drivers/net/DriverNetHomeAssistant.cpp @@ -0,0 +1,313 @@ +#include + +#include +#ifdef ENABLE_BONJOUR + #include +#endif + +DriverNetHomeAssistant::DriverNetHomeAssistant(const QJsonObject& deviceConfig) + : LedDevice(deviceConfig) +{ +} + +LedDevice* DriverNetHomeAssistant::construct(const QJsonObject& deviceConfig) +{ + return new DriverNetHomeAssistant(deviceConfig); +} + +bool DriverNetHomeAssistant::init(const QJsonObject& deviceConfig) +{ + bool isInitOK = false; + + if (LedDevice::init(deviceConfig)) + { + + _haInstance.homeAssistantHost = deviceConfig["homeAssistantHost"].toString(); + _haInstance.longLivedAccessToken = deviceConfig["longLivedAccessToken"].toString(); + _haInstance.transition = deviceConfig["transition"].toInt(0); + _haInstance.constantBrightness = deviceConfig["constantBrightness"].toInt(0); + _haInstance.restoreOriginalState = deviceConfig["restoreOriginalState"].toBool(false); + _maxRetry = deviceConfig["maxRetry"].toInt(60); + + QUrl url("http://" + _haInstance.homeAssistantHost); + _restApi = std::make_unique(url.host(), url.port(8123)); + _restApi->addHeader("Authorization", QString("Bearer %1").arg(_haInstance.longLivedAccessToken)); + + Debug(_log, "HomeAssistantHost : %s", QSTRING_CSTR(_haInstance.homeAssistantHost)); + Debug(_log, "RestoreOriginalState : %s", (_haInstance.restoreOriginalState) ? "yes" : "no"); + Debug(_log, "Transition (ms) : %s", (_haInstance.transition > 0) ? QSTRING_CSTR(QString::number(_haInstance.transition)) : "disabled" ); + Debug(_log, "ConstantBrightness : %s", (_haInstance.constantBrightness > 0) ? QSTRING_CSTR(QString::number(_haInstance.constantBrightness)) : "disabled"); + Debug(_log, "Max retry : %d", _maxRetry); + + auto arr = deviceConfig["lamps"].toArray(); + + for (const auto&& lamp : arr) + if (lamp.isObject()) + { + HomeAssistantLamp hl; + auto lampObj = lamp.toObject(); + hl.name = lampObj["name"].toString(); + hl.colorModel = static_cast(lampObj["colorModel"].toInt(0)); + Debug(_log, "Configured lamp (%s) : %s", (hl.colorModel == 0) ? "RGB" : "HSV", QSTRING_CSTR(hl.name)); + _haInstance.lamps.push_back(hl); + } + + if (_haInstance.homeAssistantHost.length() > 0 && _haInstance.longLivedAccessToken.length() > 0 && arr.size() > 0) + { + isInitOK = true; + } + } + return isInitOK; +} + + +bool DriverNetHomeAssistant::powerOnOff(bool isOn) +{ + QJsonDocument doc; + QJsonObject row; + QJsonArray entities; + + for (const auto& lamp : _haInstance.lamps) + { + entities.push_back(lamp.name); + } + + row["entity_id"] = entities; + doc.setObject(row); + + QString message(doc.toJson(QJsonDocument::Compact)); + _restApi->setBasePath(QString("/api/services/light/%1").arg((isOn) ? "turn_on" : "turn_off")); + auto response = _restApi->post(message); + + if (response.error()) + { + this->setInError(response.error() ? response.getErrorReason() : "Unknown"); + setupRetry(5000); + return false; + } + + return true; +} + +bool DriverNetHomeAssistant::powerOn() +{ + if (_haInstance.restoreOriginalState) + { + if (!saveStates()) + return false; + } + return powerOnOff(true); +} + +bool DriverNetHomeAssistant::powerOff() +{ + if (_haInstance.restoreOriginalState) + { + restoreStates(); + return true; + } + return powerOnOff(false); +} + +int DriverNetHomeAssistant::write(const std::vector& ledValues) +{ + QJsonDocument doc; + + auto rgb = ledValues.begin(); + for (const auto& lamp : _haInstance.lamps) + if (rgb != ledValues.end()) + { + QJsonObject row; + auto& color = *(rgb++); + int brightness = 0; + + row["entity_id"] = lamp.name; + + if (_haInstance.transition > 0) + { + row["transition"] = _haInstance.transition / 1000.0; + } + + if (lamp.colorModel == HomeAssistantLamp::Mode::RGB) + { + row["rgb_color"] = QJsonArray{ color.red, color.green, color.blue }; + brightness = std::min(std::max(static_cast(std::roundl(0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue)), 0), 255); + } + else + { + uint16_t h; + float s, v; + color.rgb2hsl(color.red, color.green, color.blue, h, s, v); + row["hs_color"] = QJsonArray{ h, static_cast(std::roundl(s * 100.0)) }; + brightness = std::min(std::max(static_cast(std::roundl(v * 255.0)), 0), 255); + } + + if (brightness > 0 && _haInstance.constantBrightness > 0) + { + brightness = _haInstance.constantBrightness; + } + + row["brightness"] = brightness; + + doc.setObject(row); + QString message(doc.toJson(QJsonDocument::Compact)); + _restApi->setBasePath("/api/services/light/turn_on"); + auto response = _restApi->post(message); + + if (response.error()) + { + this->setInError(response.error() ? response.getErrorReason() : "Unknown"); + setupRetry(5000); + return false; + } + } + + return 0; +} + +bool DriverNetHomeAssistant::saveStates() +{ + for (auto& lamp : _haInstance.lamps) + { + _restApi->setBasePath(QString("/api/states/%1").arg(lamp.name)); + auto response = _restApi->get(); + if (response.error()) + { + this->setInError(response.error() ? response.getErrorReason() : "Unknown"); + setupRetry(5000); + return false; + } + auto body = response.getBody(); + if (body.isEmpty() || !body.isObject()) + { + Error(_log, "The current state of the light %s is unknown", QSTRING_CSTR(lamp.name)); + continue; + } + + // read state + auto obj = body.object(); + if (obj.contains("state") && !obj["state"].isNull()) + { + lamp.orgState.isPoweredOn = QString::compare(obj["state"].toString("off"), QString("on"), Qt::CaseInsensitive) == 0; + } + else + { + lamp.orgState.isPoweredOn = -1; + } + + if (obj.contains("attributes") && obj["attributes"].isObject()) + { + auto attribs = obj["attributes"].toObject(); + + // read brightness + if (attribs.contains("brightness") && !attribs["brightness"].isNull()) + { + lamp.orgState.brightness = attribs["brightness"].toInt(0); + } + else + { + lamp.orgState.brightness = -1; + } + + // read color + if (lamp.colorModel == HomeAssistantLamp::Mode::RGB && + attribs.contains("rgb_color") && attribs["rgb_color"].isArray()) + { + lamp.orgState.color = attribs["rgb_color"].toArray(); + } + else if (lamp.colorModel == HomeAssistantLamp::Mode::HSV && + attribs.contains("hs_color") && attribs["hs_color"].isArray()) + { + lamp.orgState.color = attribs["hs_color"].toArray(); + } + else + { + lamp.orgState.color = QJsonArray(); + } + } + + QJsonDocument doc; + doc.setArray(lamp.orgState.color); + QString colorsToString = ((lamp.colorModel == HomeAssistantLamp::Mode::RGB) ? "rgb: " : "hs: " )+ doc.toJson(QJsonDocument::Compact); + QString power = (lamp.orgState.isPoweredOn >= 0) ? ((lamp.orgState.isPoweredOn) ? "state: ON" : "state: OFF" ) : ""; + QString brightness = (lamp.orgState.isPoweredOn >= 0) ? (QString("brightness: %1" ).arg(lamp.orgState.brightness)) : ""; + QStringList message{ power, brightness, colorsToString }; + + Info(_log, "Saving state of %s: %s", QSTRING_CSTR(lamp.name), QSTRING_CSTR(message.join(", "))); + + } + return true; +} + +void DriverNetHomeAssistant::restoreStates() +{ + QJsonDocument doc; + for (auto& lamp : _haInstance.lamps) + { + QJsonObject row; + + row["entity_id"] = lamp.name; + + if (lamp.orgState.isPoweredOn < 0) + continue; + + if (lamp.orgState.brightness >= 0) + { + row["brightness"] = lamp.orgState.brightness; + } + if (lamp.orgState.color.size() > 0) + { + if (lamp.colorModel == HomeAssistantLamp::Mode::RGB) + { + row["rgb_color"] = lamp.orgState.color; + } + else + { + row["hs_color"] = lamp.orgState.color; + } + } + + if (!row.isEmpty()) + { + doc.setObject(row); + QString message = doc.toJson(QJsonDocument::Compact); + Info(_log, "Restoring state of %s: %s", QSTRING_CSTR(lamp.name), QSTRING_CSTR(message)); + _restApi->setBasePath(QString("/api/services/light/%1").arg((lamp.orgState.isPoweredOn) ? "turn_on" : "turn_off")); + _restApi->post(message); + } + } +} + +QJsonObject DriverNetHomeAssistant::discover(const QJsonObject& params) +{ + QJsonObject devicesDiscovered; + QJsonArray deviceList; + devicesDiscovered.insert("ledDeviceType", _activeDeviceType); + +#ifdef ENABLE_BONJOUR + std::shared_ptr bonInstance = _discoveryWrapper.lock(); + if (bonInstance != nullptr) + { + QList recs; + + SAFE_CALL_0_RET(bonInstance.get(), getHomeAssistant, QList, recs); + + for (DiscoveryRecord& r : recs) + { + QJsonObject newIp; + newIp["value"] = QString("%1:8123").arg(r.address); + newIp["name"] = QString("%1 (%2)").arg(newIp["value"].toString()).arg(r.hostName); + deviceList.push_back(newIp); + } + } +#else + Error(_log, "The Network Discovery Service was mysteriously disabled while the maintainer was compiling this version of HyperHDR"); +#endif + + devicesDiscovered.insert("devices", deviceList); + Debug(_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); + + return devicesDiscovered; +} + +bool DriverNetHomeAssistant::isRegistered = hyperhdr::leds::REGISTER_LED_DEVICE("home_assistant", "leds_group_2_network", DriverNetHomeAssistant::construct); diff --git a/sources/led-drivers/schemas/schema-home_assistant.json b/sources/led-drivers/schemas/schema-home_assistant.json new file mode 100644 index 000000000..3db278127 --- /dev/null +++ b/sources/led-drivers/schemas/schema-home_assistant.json @@ -0,0 +1,96 @@ +{ + "type":"object", + "required":true, + "properties":{ + "homeAssistantHost" : { + "type": "string", + "title":"edt_dev_spec_targetIpHost_title", + "required" : true, + "propertyOrder" : 1 + }, + "longLivedAccessToken" : { + "type": "string", + "title":"edt_dev_auth_key_title", + "required" : true, + "propertyOrder" : 2 + }, + "transition": { + "type": "integer", + "title": "edt_dev_spec_transistionTime_title", + "default": 0, + "append": "ms", + "minimum": 0, + "maximum": 3000, + "required": true, + "propertyOrder": 3 + }, + "constantBrightness": { + "type": "integer", + "title": "edt_dev_spec_constantBrightness_title", + "default": 0, + "minimum": 0, + "maximum": 255, + "required": true, + "propertyOrder": 4 + }, + "restoreOriginalState": { + "type": "boolean", + "format": "checkbox", + "title":"edt_dev_spec_restoreOriginalState_title", + "default" : false, + "propertyOrder" : 5 + }, + "maxRetry": { + "type" : "integer", + "format" : "stepper", + "step" : 1, + "title" : "edt_dev_max_retry", + "minimum" : 0, + "maximum" : 300, + "default" : 60, + "required" : true, + "propertyOrder" : 6 + }, + "lamps": { + "type": "array", + "title":"edt_dev_spec_lights_title", + "propertyOrder" : 7, + "uniqueItems" : true, + "items" : { + "type" : "object", + "title" : "edt_dev_spec_lights_itemtitle", + "required" : [ + "name", "colorModel" + ], + "properties" : + { + "name" : + { + "type": "string", + "title" : "edt_dev_spec_lights_name", + "propertyOrder" : 1 + }, + "colorModel": { + "type": "integer", + "title":"edt_conf_bb_mode_title", + "enum" : [0, 1], + "default" : 0, + "options" : { + "enum_titles" : ["edt_conf_enum_rgb", "edt_conf_enum_hsv"] + }, + "propertyOrder" : 2 + }, + "defaultPosition" : + { + "type": "string", + "options": { + "hidden": true + }, + "propertyOrder" : 3 + } + } + } + } + }, + "additionalProperties": true +} diff --git a/sources/led-drivers/schemas/schema-skydimo.json b/sources/led-drivers/schemas/schema-skydimo.json index 349bce23a..f3df6d531 100644 --- a/sources/led-drivers/schemas/schema-skydimo.json +++ b/sources/led-drivers/schemas/schema-skydimo.json @@ -11,7 +11,7 @@ "rate": { "type": "integer", "title":"edt_dev_spec_baudrate_title", - "default": 1000000, + "default": 115200, "propertyOrder" : 2 }, "delayAfterConnect": { diff --git a/www/i18n/en.json b/www/i18n/en.json index da484516a..dbe8bdde1 100644 --- a/www/i18n/en.json +++ b/www/i18n/en.json @@ -1263,5 +1263,9 @@ "edt_automatic_tone_mapping_time_title": "Time to turn on tone mapping", "edt_automatic_tone_mapping_time_expl": "Time to turn on tone mapping if the signal does not exceed the configured threshold levels.", "edt_automatic_tone_mapping_disable_time_title": "Time to turn off tone mapping", - "edt_automatic_tone_mapping_disable_time_expl": "In an ideal world this value should be zero, because tone mapping should be disabled immediately after crossing one of the thresholds. But it happens, for example: when starting or switching resolution, the grabber can generate junk frames, which can disable tone mapping unnecessarily." + "edt_automatic_tone_mapping_disable_time_expl": "In an ideal world this value should be zero, because tone mapping should be disabled immediately after crossing one of the thresholds. But it happens, for example: when starting or switching resolution, the grabber can generate junk frames, which can disable tone mapping unnecessarily.", + "wiz_home_assistant_title": "Home Assistant lights wizard", + "wiz_ha_intro": "Please select the address of the Home Assistant instance and enter the 'Long Lived Access Tokens' created there.", + "select_ha_intro": "Select Home Assistant", + "edt_dev_spec_constantBrightness_title" : "Constant brightness" } diff --git a/www/js/hyperhdr.js b/www/js/hyperhdr.js index 7707699b6..a0a5a44df 100644 --- a/www/js/hyperhdr.js +++ b/www/js/hyperhdr.js @@ -662,3 +662,23 @@ async function requestHasLedClock() { sendToHyperhdr("leddevice", "hasLedClock", `"ledDeviceType": "", "params": {}`, Math.floor(Math.random() * 1000)); } + +async function tunnel_home_assistant_get(_ip, _path,header={}) +{ + let data = { service: "home_assistant", ip: _ip, path: _path, data: "", header }; + let r = await sendAsyncToHyperhdr("tunnel", "get", data, Math.floor(Math.random() * 1000)); + if (r["success"] != true || r["isTunnelOk"] != true) + return null; + else + return r["info"]; +} + +async function tunnel_home_assistant_post(_ip, _path, header={}, _data) +{ + let data = { service: "home_assistant", ip: _ip, path: _path, data: _data, header }; + let r = await sendAsyncToHyperhdr("tunnel", "post", data, Math.floor(Math.random() * 1000)); + if (r["success"] != true || r["isTunnelOk"] != true) + return null; + else + return r["info"]; +} diff --git a/www/js/light_source.js b/www/js/light_source.js index 226b5d864..92e94a916 100644 --- a/www/js/light_source.js +++ b/www/js/light_source.js @@ -26,6 +26,42 @@ if (typeof ResizeObserver === "function" && _resizeObserver === null) }); } +async function deviceListRefresh(ledTypeTarget, discoveryResult, targetDiscoveryEditor, targetDiscoveryFirstLabel, targetDiscoverySecondLabel = 'main_menu_update_token') +{ + let receiver = $("#deviceListInstances"); + receiver.off(); + receiver.empty(); + + $("