From 0c42549c468f4abd7b74bdf3125030acb3bc28ce Mon Sep 17 00:00:00 2001 From: gene Date: Wed, 20 Mar 2024 22:29:09 +0100 Subject: [PATCH] v1.2.6 - Updated smart shutters example (using 360 Servo) --- examples/color-light/api/ColorLight.cpp | 101 ------------- examples/color-light/api/ColorLight.h | 116 --------------- examples/color-light/api/Dimmer.cpp | 78 ---------- examples/color-light/api/Dimmer.h | 81 ---------- examples/color-light/api/Switch.cpp | 103 ------------- examples/color-light/api/Switch.h | 63 -------- examples/color-light/color-light.cpp | 3 +- examples/color-light/configuration.h | 12 +- examples/ir-transceiver/configuration.h | 4 + examples/ir-transceiver/ir-transceiver.cpp | 30 +++- examples/shutter/api/ShutterHandler.cpp | 19 +++ examples/shutter/configuration.h | 11 ++ examples/shutter/io/IShutterDriver.h | 2 + examples/shutter/io/ShutterControl.cpp | 10 +- examples/shutter/io/ShutterControl.h | 3 +- examples/shutter/io/drivers/ServoDriver.h | 140 +++++++++++------- examples/shutter/shutter.cpp | 53 ++++++- examples/smart-sensor-display/configuration.h | 2 + .../smart-sensor-display.cpp | 2 +- platformio.ini | 21 ++- src/io/IOEventPaths.h | 1 + src/service/api/devices/ColorLight.cpp | 104 +++++++++++++ src/service/api/devices/ColorLight.h | 117 +++++++++++++++ src/service/api/devices/Dimmer.cpp | 81 ++++++++++ src/service/api/devices/Dimmer.h | 83 +++++++++++ src/service/api/devices/Switch.cpp | 108 ++++++++++++++ src/service/api/devices/Switch.h | 65 ++++++++ 27 files changed, 790 insertions(+), 623 deletions(-) delete mode 100644 examples/color-light/api/ColorLight.cpp delete mode 100644 examples/color-light/api/ColorLight.h delete mode 100644 examples/color-light/api/Dimmer.cpp delete mode 100644 examples/color-light/api/Dimmer.h delete mode 100644 examples/color-light/api/Switch.cpp delete mode 100644 examples/color-light/api/Switch.h create mode 100644 src/service/api/devices/ColorLight.cpp create mode 100644 src/service/api/devices/ColorLight.h create mode 100644 src/service/api/devices/Dimmer.cpp create mode 100644 src/service/api/devices/Dimmer.h create mode 100644 src/service/api/devices/Switch.cpp create mode 100644 src/service/api/devices/Switch.h diff --git a/examples/color-light/api/ColorLight.cpp b/examples/color-light/api/ColorLight.cpp deleted file mode 100644 index a8f5247..0000000 --- a/examples/color-light/api/ColorLight.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * HomeGenie-Mini (c) 2018-2024 G-Labs - * - * - * This file is part of HomeGenie-Mini (HGM). - * - * HomeGenie-Mini is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * HomeGenie-Mini is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with HomeGenie-Mini. If not, see . - * - * - * Authors: - * - Generoso Martello - * - */ - -#include "ColorLight.h" - - -ColorLight::ColorLight(const char* domain, const char* address, const char* name): Dimmer(domain, address, name) { - module->type = "Color"; - - // add properties - auto propStatusColorHsb = new ModuleParameter(IOEventPaths::Status_ColorHsb); - module->properties.add(propStatusColorHsb); - - onSetLevel([this](float l) { - color.setColor(color.getHue(), color.getSaturation(), l, defaultTransitionMs); - }); -} - -void ColorLight::loop() { - Dimmer::loop(); // parent - - if (color.isAnimating) { - if (setColorCallback != nullptr) { - setColorCallback(color.getRed(), color.getGreen(), color.getBlue()); - } - } - -} - -bool ColorLight::handleRequest(APIRequest* command, ResponseCallback* responseCallback) { - if (Dimmer::handleRequest(command, responseCallback)) return true; - - auto m = getModule(command->Domain.c_str(), command->Address.c_str()); - if (m != nullptr && command->Command == ControlApi::Control_ColorHsb) { - - auto hsvString = command->getOption(0); - - float o[4]; int oi = 0; - int ci; - do { - ci = hsvString.indexOf(","); - if (ci <= 0) { - o[oi] = hsvString.toFloat(); - break; - } - o[oi++] = hsvString.substring(0, ci).toFloat(); - hsvString = hsvString.substring(ci + 1); - } while (oi < 4); - - - color.setColor(o[0], o[1], o[2], o[3]*1000); - - - // Event Stream Message Enqueue (for MQTT/SSE/WebSocket propagation) - auto eventValue = command->getOption(0); - auto msg = QueuedMessage(m, IOEventPaths::Status_ColorHsb, eventValue, nullptr, IOEventDataType::Undefined); - m->setProperty(IOEventPaths::Status_ColorHsb, eventValue, nullptr, IOEventDataType::Undefined); - HomeGenie::getInstance()->getEventRouter().signalEvent(msg); - // level prop - auto levelValue = String(o[2]); // TODO: use sprintf %.6f - auto msg2 = QueuedMessage(m, IOEventPaths::Status_Level, levelValue, nullptr, IOEventDataType::Undefined); - m->setProperty(IOEventPaths::Status_Level, levelValue, nullptr, IOEventDataType::Undefined); - HomeGenie::getInstance()->getEventRouter().signalEvent(msg2); - - if (o[2] > 0) { - Switch::status = SWITCH_STATUS_ON; - Switch::onLevel = o[2]; - } else { - Switch::status = SWITCH_STATUS_OFF; - } - - responseCallback->writeAll(R"({ "ResponseText": "OK" })"); - return true; - - } - // not handled - return false; -} - diff --git a/examples/color-light/api/ColorLight.h b/examples/color-light/api/ColorLight.h deleted file mode 100644 index 543243b..0000000 --- a/examples/color-light/api/ColorLight.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * HomeGenie-Mini (c) 2018-2024 G-Labs - * - * - * This file is part of HomeGenie-Mini (HGM). - * - * HomeGenie-Mini is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * HomeGenie-Mini is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with HomeGenie-Mini. If not, see . - * - * - * Authors: - * - Generoso Martello - * - */ - -#ifndef HOMEGENIE_MINI_COLORLIGHT_H -#define HOMEGENIE_MINI_COLORLIGHT_H - -#include -#include - -#include "Dimmer.h" - -using namespace Service; - -class LightColor { -public: - unsigned long duration; - bool isAnimating = false; - void setColor(float hue, float saturation, float value, unsigned long transitionMs) { - duration = transitionMs; - oh = h; - os = s; - ov = v; - h = hue; - s = saturation; - v = value; - startTime = millis(); - isAnimating = true; - } - float getProgress() { - float p = (float)(millis() - startTime) / (float)duration; - if (p >= 1) { - isAnimating = false; - p = 1; - } - return p; - } - float getHue() { - return oh + ((h - oh) * getProgress()); - } - float getSaturation() { - return os + ((s - os) * getProgress()); - } - float getValue() { - return ov + ((v - ov) * getProgress()); - } - float getRed() { - auto orgb = Utility::hsv2rgb(hfix(oh), os, ov); - auto crgb = Utility::hsv2rgb(hfix(h), s, v); - float r = orgb.r + ((crgb.r - orgb.r) * getProgress()); - return r; - } - float getGreen() { - auto orgb = Utility::hsv2rgb(hfix(oh), os, ov); - auto crgb = Utility::hsv2rgb(hfix(h), s, v); - float g = orgb.g + ((crgb.g - orgb.g) * getProgress()); - return g; - } - float getBlue() { - auto orgb = Utility::hsv2rgb(hfix(oh), os, ov); - auto crgb = Utility::hsv2rgb(hfix(h), s, v); - float b = orgb.b + ((crgb.b - orgb.b) * getProgress()); - return b; - } -private: - float h; - float s; - float v; - float oh, os, ov; - unsigned long startTime; - float hfix(float h) { - return 1.325f - h; - } - -}; - -class ColorLight: public Dimmer { -public: - ColorLight(const char* domain, const char* address, const char* name); - - void loop() override; - bool handleRequest(APIRequest*, ResponseCallback*) override; - - void onSetColor(std::function callback) { - setColorCallback = std::move(callback); - } -private: - LightColor color; - std::function setColorCallback = nullptr; - - void setColor(float h, float s, float v, float duration); -}; - - -#endif //HOMEGENIE_MINI_COLORLIGHT_H diff --git a/examples/color-light/api/Dimmer.cpp b/examples/color-light/api/Dimmer.cpp deleted file mode 100644 index b7d2f4a..0000000 --- a/examples/color-light/api/Dimmer.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * HomeGenie-Mini (c) 2018-2024 G-Labs - * - * - * This file is part of HomeGenie-Mini (HGM). - * - * HomeGenie-Mini is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * HomeGenie-Mini is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with HomeGenie-Mini. If not, see . - * - * - * Authors: - * - Generoso Martello - * - */ - -#include "Dimmer.h" - - -Dimmer::Dimmer(const char* domain, const char* address, const char* name): Switch(domain, address, name) { - setLoopInterval(10); // fixed transition frequency - module->type = "Dimmer"; - onSetStatus([this](SwitchStatus status) { - level.setLevel(status == SWITCH_STATUS_ON ? 1 : 0, defaultTransitionMs); - }); -} - -void Dimmer::loop() { - - if (level.isAnimating) { - if (setLevelCallback != nullptr) { - setLevelCallback(level.getLevel()); - } - } - -} - -bool Dimmer::handleRequest(APIRequest *command, ResponseCallback* responseCallback) { - if (Switch::handleRequest(command, responseCallback)) return true; - - auto m = getModule(command->Domain.c_str(), command->Address.c_str()); - if (m != nullptr && command->Command == ControlApi::Control_Level) { - - auto l = command->getOption(0).toFloat() / 100.0F; // 0.00 - 1.00 0 = OFF, 1.00 = MAX - auto transition = command->getOption(1).isEmpty() ? defaultTransitionMs : command->getOption(1).toFloat(); // ms - - level.setLevel(l, transition); - - // Event Stream Message Enqueue (for MQTT/SSE/WebSocket propagation) - auto eventPath = IOEventPaths::Status_Level; - auto eventValue = String(l); - auto msg = QueuedMessage(m, eventPath, eventValue, &l, IOEventDataType::Float); - m->setProperty(eventPath, eventValue, &l, IOEventDataType::Float); - HomeGenie::getInstance()->getEventRouter().signalEvent(msg); - - if (l > 0) { - Switch::status = SWITCH_STATUS_ON; - Switch::onLevel = l; - } else { - Switch::status = SWITCH_STATUS_OFF; - } - - responseCallback->writeAll(R"({ "ResponseText": "OK" })"); - return true; - - } - // not handled - return false; -} diff --git a/examples/color-light/api/Dimmer.h b/examples/color-light/api/Dimmer.h deleted file mode 100644 index 43b197e..0000000 --- a/examples/color-light/api/Dimmer.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * HomeGenie-Mini (c) 2018-2024 G-Labs - * - * - * This file is part of HomeGenie-Mini (HGM). - * - * HomeGenie-Mini is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * HomeGenie-Mini is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with HomeGenie-Mini. If not, see . - * - * - * Authors: - * - Generoso Martello - * - */ - -#ifndef HOMEGENIE_MINI_DIMMER_H -#define HOMEGENIE_MINI_DIMMER_H - -#include - -#include "Switch.h" - -using namespace Service; - -class DimmerLevel { -public: - unsigned long duration; - bool isAnimating = false; - void setLevel(float l, unsigned long transitionMs) { - duration = transitionMs; - ol = level; - level = l; - startTime = millis(); - isAnimating = true; - } - float getProgress() { - float p = (float)(millis() - startTime) / (float)duration; - if (p >= 1) { - isAnimating = false; - p = 1; - } - return p; - } - float getLevel() { - return ol + ((level - ol) * getProgress()); - } -private: - float level; - float ol; - unsigned long startTime; - -}; - -class Dimmer: public Switch { -public: - Dimmer(const char* domain, const char* address, const char* name); - void loop() override; - - bool handleRequest(APIRequest*, ResponseCallback*) override; - - void onSetLevel(std::function callback) { - setLevelCallback = std::move(callback); - } -private: - DimmerLevel level; - std::function setLevelCallback = nullptr; -protected: - const unsigned long defaultTransitionMs = 500; -}; - -#endif //HOMEGENIE_MINI_DIMMER_H diff --git a/examples/color-light/api/Switch.cpp b/examples/color-light/api/Switch.cpp deleted file mode 100644 index c1af1ab..0000000 --- a/examples/color-light/api/Switch.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* - * HomeGenie-Mini (c) 2018-2024 G-Labs - * - * - * This file is part of HomeGenie-Mini (HGM). - * - * HomeGenie-Mini is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * HomeGenie-Mini is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with HomeGenie-Mini. If not, see . - * - * - * Authors: - * - Generoso Martello - * - */ - -#include "Switch.h" - -Switch::Switch(const char* domain, const char* address, const char* name) { - module = new Module(); - module->domain = domain; - module->address = address; - module->type = "Switch"; - module->name = name; - // add properties - auto propStatusLevel = new ModuleParameter(IOEventPaths::Status_Level); - module->properties.add(propStatusLevel); - - moduleList.add(module); -} - -void Switch::init() { -} - -bool Switch::handleRequest(APIRequest *command, ResponseCallback* responseCallback) { - auto m = getModule(command->Domain.c_str(), command->Address.c_str()); - if (m != nullptr) { - - auto eventPath = IOEventPaths::Status_Level; - SwitchStatus s = SWITCH_STATUS_NOT_SET; - if (command->Command == ControlApi::Control_On) { - s = SWITCH_STATUS_ON; - } else if (command->Command == ControlApi::Control_Off) { - s = SWITCH_STATUS_OFF; - } else if (command->Command == ControlApi::Control_Toggle) { - s = status == SWITCH_STATUS_ON ? SWITCH_STATUS_OFF : SWITCH_STATUS_ON; - } - - if (s != SWITCH_STATUS_NOT_SET) { - status = s; - - if (setStatusCallback != nullptr) { - setStatusCallback(status); - } - - // Event Stream Message Enqueue (for MQTT/SSE/WebSocket propagation) - float l = status == SWITCH_STATUS_ON ? onLevel : 0; - auto eventValue = String(l); - auto msg = QueuedMessage(m, eventPath, eventValue, &l, IOEventDataType::Float); - m->setProperty(eventPath, eventValue, &l, IOEventDataType::Float); - HomeGenie::getInstance()->getEventRouter().signalEvent(msg); - - responseCallback->writeAll(R"({ "ResponseText": "OK" })"); - - return true; - } - - } - // not handled - return false; -} - -bool Switch::canHandleDomain(String* domain) { - return domain->equals(IO::IOEventDomains::HomeAutomation_HomeGenie); -} - -bool Switch::handleEvent(IIOEventSender *sender, - const char* domain, const char* address, - const unsigned char *eventPath, void *eventData, IOEventDataType dataType) { - return false; -} - -Module* Switch::getModule(const char* domain, const char* address) { - for (int i = 0; i < moduleList.size(); i++) { - Module* m = moduleList.get(i); - if (m->domain.equals(domain) && m->address.equals(address)) - return m; - } - return nullptr; -} - -LinkedList* Switch::getModuleList() { - return &moduleList; -} diff --git a/examples/color-light/api/Switch.h b/examples/color-light/api/Switch.h deleted file mode 100644 index 71978bf..0000000 --- a/examples/color-light/api/Switch.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * HomeGenie-Mini (c) 2018-2024 G-Labs - * - * - * This file is part of HomeGenie-Mini (HGM). - * - * HomeGenie-Mini is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * HomeGenie-Mini is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with HomeGenie-Mini. If not, see . - * - * - * Authors: - * - Generoso Martello - * - */ - -#ifndef HOMEGENIE_MINI_SWITCH_H -#define HOMEGENIE_MINI_SWITCH_H - -#include - -using namespace Service; - -enum SwitchStatus { - SWITCH_STATUS_NOT_SET = -1, - SWITCH_STATUS_OFF, - SWITCH_STATUS_ON, -}; - -class Switch: public Task, public APIHandler { -public: - Switch(const char* domain, const char* address, const char* name); - void init() override; - bool canHandleDomain(String* domain) override; - bool handleRequest(APIRequest*, ResponseCallback*) override; - bool handleEvent(IIOEventSender*, - const char* domain, const char* address, - const unsigned char *eventPath, void* eventData, IOEventDataType) override; - - Module* getModule(const char* domain, const char* address) override; - LinkedList* getModuleList() override; - void onSetStatus(std::function callback) { - setStatusCallback = std::move(callback); - } -private: - LinkedList moduleList; - std::function setStatusCallback = nullptr; -protected: - Module* module; - SwitchStatus status = SWITCH_STATUS_NOT_SET; - float onLevel = 1; -}; - -#endif //HOMEGENIE_MINI_SWITCH_H diff --git a/examples/color-light/color-light.cpp b/examples/color-light/color-light.cpp index 9f1d6da..a7da3f8 100644 --- a/examples/color-light/color-light.cpp +++ b/examples/color-light/color-light.cpp @@ -24,14 +24,15 @@ */ #include +#include #include #include "configuration.h" -#include "api/ColorLight.h" using namespace Service; +using namespace Service::API::devices; HomeGenie* homeGenie; diff --git a/examples/color-light/configuration.h b/examples/color-light/configuration.h index c60a017..a62fc18 100644 --- a/examples/color-light/configuration.h +++ b/examples/color-light/configuration.h @@ -1,11 +1,3 @@ -#define CONFIG_IRReceiverPin 5 -#define CONFIG_IRTransmitterPin 7 - -#define CONFIG_IR_MODULE_ADDRESS "IR" - -#ifdef MINI_ESP32 - -#define CONFIG_IRTransmitterPin 22 -#define CONFIG_IRReceiverPin 21 - +#ifndef CONFIG_StatusLedNeoPixelPin +#define CONFIG_StatusLedNeoPixelPin 10 #endif diff --git a/examples/ir-transceiver/configuration.h b/examples/ir-transceiver/configuration.h index c60a017..0c0b7cf 100644 --- a/examples/ir-transceiver/configuration.h +++ b/examples/ir-transceiver/configuration.h @@ -1,6 +1,10 @@ #define CONFIG_IRReceiverPin 5 #define CONFIG_IRTransmitterPin 7 +#ifndef CONFIG_StatusLedNeoPixelPin +#define CONFIG_StatusLedNeoPixelPin 10 +#endif + #define CONFIG_IR_MODULE_ADDRESS "IR" #ifdef MINI_ESP32 diff --git a/examples/ir-transceiver/ir-transceiver.cpp b/examples/ir-transceiver/ir-transceiver.cpp index 6b327f8..049daf1 100644 --- a/examples/ir-transceiver/ir-transceiver.cpp +++ b/examples/ir-transceiver/ir-transceiver.cpp @@ -32,14 +32,20 @@ #include "io/IRTransmitter.h" #include "api/IRTransceiverHandler.h" +#ifdef ESP32_C3 +#include +#endif + using namespace Service; HomeGenie* homeGenie; + #ifdef ESP32_C3 +using namespace Service::API::devices; #include // Custom status led (builtin NeoPixel RGB on pin 10) -Adafruit_NeoPixel pixels(1, 10, NEO_GRB + NEO_KHZ800); +Adafruit_NeoPixel pixels(1, CONFIG_StatusLedNeoPixelPin, NEO_GRB + NEO_KHZ800); void statusLedCallback(bool isLedOn) { if (isLedOn) { pixels.setPixelColor(0, Adafruit_NeoPixel::Color(50, 50, 0)); @@ -48,12 +54,16 @@ void statusLedCallback(bool isLedOn) { } pixels.show(); } +unsigned long helloWorldDuration = 10000; +bool helloWorldActive = true; #endif void setup() { #ifdef ESP32_C3 // Custom status led (builtin NeoPixel RGB on pin 10) - Config::statusLedCallback(&statusLedCallback); +// if (!Config::isDeviceConfigured()) { + Config::statusLedCallback(&statusLedCallback); +// } pixels.begin(); #endif @@ -67,10 +77,26 @@ void setup() { auto transmitter = new IR::IRTransmitter(transmitterConfig); homeGenie->addAPIHandler(new IRTransceiverHandler(transmitter, receiver)); +#ifdef ESP32_C3 + auto colorLight = new ColorLight(IO::IOEventDomains::HomeAutomation_HomeGenie, "C1", "Demo Light"); + colorLight->onSetColor([](float r, float g, float b) { + pixels.setPixelColor(0, r, g, b); + pixels.show(); + }); + homeGenie->addAPIHandler(colorLight); +#endif + homeGenie->begin(); } + void loop() { homeGenie->loop(); +#ifdef ESP32_C3 + if (helloWorldActive && millis() > helloWorldDuration && Config::isDeviceConfigured()) { + helloWorldActive = false; + Config::statusLedCallback(nullptr); + } +#endif } diff --git a/examples/shutter/api/ShutterHandler.cpp b/examples/shutter/api/ShutterHandler.cpp index a29f25b..c6e813c 100644 --- a/examples/shutter/api/ShutterHandler.cpp +++ b/examples/shutter/api/ShutterHandler.cpp @@ -45,6 +45,8 @@ namespace Service { namespace API { shutterLevel = new ModuleParameter(IOEventPaths::Status_Level); shutterModule->properties.add(shutterLevel); + shutterControl->setModule(shutterModule); + moduleList.add(shutterModule); } @@ -70,10 +72,24 @@ namespace Service { namespace API { shutterControl->close(); + responseCallback->writeAll(R"({ "ResponseText": "OK" })"); + } else if (command->Command == "Control.Open" || command->Command == "Control.On") { shutterControl->open(); + responseCallback->writeAll(R"({ "ResponseText": "OK" })"); + + } else if (command->Command == "Shutter.Speed") { + + shutterControl->setSpeed(command->OptionsString.toFloat()); + + responseCallback->writeAll(R"({ "ResponseText": "OK" })"); + + } else if (command->Command == "Shutter.Calibrate") { + + // TODO: ... + } else { return false; @@ -106,6 +122,9 @@ namespace Service { namespace API { case Float: m.value = String(*(float *) eventData); break; + case Text: + m.value = String(*(String *) eventData); + break; default: m.value = String(*(int32_t *) eventData); } diff --git a/examples/shutter/configuration.h b/examples/shutter/configuration.h index e69de29..0c16ae2 100644 --- a/examples/shutter/configuration.h +++ b/examples/shutter/configuration.h @@ -0,0 +1,11 @@ +#ifndef CONFIG_StatusLedNeoPixelPin +#define CONFIG_StatusLedNeoPixelPin 10 +#endif + +// TODO: #define CONFIG_StepperMotorPinXX YY + +#ifdef ESP32_C3 + #define CONFIG_ServoMotorPin 5 +#else + #define CONFIG_ServoMotorPin 15 +#endif diff --git a/examples/shutter/io/IShutterDriver.h b/examples/shutter/io/IShutterDriver.h index 6464308..cf46bd8 100644 --- a/examples/shutter/io/IShutterDriver.h +++ b/examples/shutter/io/IShutterDriver.h @@ -47,6 +47,8 @@ namespace IO { namespace Components { virtual void open() = 0; virtual void close() = 0; virtual void level(float) = 0; + virtual void calibrate() = 0; + virtual void speed(float) = 0; IIOEventSender* eventSender = nullptr; }; }} diff --git a/examples/shutter/io/ShutterControl.cpp b/examples/shutter/io/ShutterControl.cpp index d300efe..027a853 100644 --- a/examples/shutter/io/ShutterControl.cpp +++ b/examples/shutter/io/ShutterControl.cpp @@ -44,14 +44,12 @@ namespace IO { namespace Components { void ShutterControl::setLevel(float level) { shutterDriver->level(level); } + void ShutterControl::setSpeed(float s) { + shutterDriver->speed(s); + } void ShutterControl::calibrate() { - // TODO: CALIBRATE WILL set the value of "totalTimeSpanMs" variable: - // TODO: --> after starting "calibration" the shutter will go up - // TODO: --> until the user confirms with any input (e.g. the "open" button) - // TODO: --> that the roller/shades are fully open. - // TODO: --> the time that the shutter took to go from position 0 to 100% will - // TODO: ---> be stored to "totalTimeSpanMs" and calibration process will be complete + // TODO: ... } }} diff --git a/examples/shutter/io/ShutterControl.h b/examples/shutter/io/ShutterControl.h index ac85c40..329fe7a 100644 --- a/examples/shutter/io/ShutterControl.h +++ b/examples/shutter/io/ShutterControl.h @@ -43,8 +43,6 @@ namespace IO { namespace Components { class ShutterControl : public IIOEventSender { private: IShutterDriver* shutterDriver; - String domain = IO::IOEventDomains::Automation_Components; - String address = SERVO_MODULE_ADDRESS; public: ShutterControl() { shutterDriver = new ServoDriver(); @@ -57,6 +55,7 @@ namespace IO { namespace Components { void open(); void close(); void setLevel(float level); + void setSpeed(float speed); void calibrate(); // TODO:.... }; diff --git a/examples/shutter/io/drivers/ServoDriver.h b/examples/shutter/io/drivers/ServoDriver.h index 32344dc..66fcd71 100644 --- a/examples/shutter/io/drivers/ServoDriver.h +++ b/examples/shutter/io/drivers/ServoDriver.h @@ -35,35 +35,46 @@ #ifndef HOMEGENIE_MINI_SERVODRIVER_H #define HOMEGENIE_MINI_SERVODRIVER_H -#include "examples/shutter/io/IShutterDriver.h" +#include -#include ".pio/libdeps/shutter/ESP32Servo/src/ESP32Servo.h" +#include + +#include "../../configuration.h" +#include "../../io/IShutterDriver.h" -#include "io/Logger.h" #define SERVO_DRIVER_NS_PREFIX "IO::Components:ShutterControl::ServoDriver" namespace IO { namespace Components { - class ServoDriver: public Task, public IShutterDriver { + + static bool ServoDriver_triggered; + + + static int lastCommand = 0; + static int revolutions = 0; + static int revolutionsMax = 17; + static bool stopRequested = false; + static unsigned long lastTickMs = 0; + static float currentLevel = 0; + static float targetLevel = -1; + static int direction = -1; // reverse open/close + + + class ServoDriver: public Task, public IShutterDriver { private: String domain = IO::IOEventDomains::Automation_Components; String address = SERVO_MODULE_ADDRESS; Servo* servoDriver; + + const int servoPin = CONFIG_ServoMotorPin; + const int frequency = 330; // Hz // Standard is 50(hz) servo - const int servoPin = 15; int minUs = 500; int maxUs = 2500; int stopUs = 1500; - int stepSpeed = 300; // [1 .. 1000] microseconds + int stepSpeed = 800; // [1 .. 1000] microseconds - int lastCommand = 0; - long lastCommandTs = 0; unsigned long lastEventMs = 0; - float currentLevel = 0; - - float totalTimeSpanMs = 20000.0f; - bool stopRequested = false; - float stopTime; public: ServoDriver() {} @@ -72,87 +83,110 @@ namespace IO { namespace Components { servoDriver = new Servo(); servoDriver->setPeriodHertz(frequency); servoDriver->attach(servoPin, minUs, maxUs); + + ServoDriver_triggered = false; + pinMode(3, INPUT_PULLUP); + attachInterrupt(3, [] { + +// TODO: should consider speed? + ServoDriver_triggered = millis() - lastTickMs > 100; + lastTickMs = millis(); + + if (ServoDriver_triggered) { + if (lastCommand != SHUTTER_COMMAND_NONE) { + revolutions += (lastCommand == SHUTTER_COMMAND_OPEN ? 1 : -1); + float level = (float)revolutions / (float)revolutionsMax; + if (revolutions <= 0 || revolutions >= revolutionsMax || (targetLevel != -1 && level == targetLevel)) { + stopRequested = true; + } + } + } + + }, CHANGE); + Logger::info("| ✔ %s", SERVO_DRIVER_NS_PREFIX); } + void calibrate() override { + + // TODO: .... + + } + void speed(float s) override { + // s -> ]0.0 .. 1.0[ + stepSpeed = s * 1000; + } void stop() override { servoDriver->write(stopUs); } void open() override { - if (lastCommand != SHUTTER_COMMAND_NONE) { + if (lastCommand != SHUTTER_COMMAND_NONE || revolutions == revolutionsMax) { stopRequested = true; } else { - servoDriver->write(stopUs + stepSpeed); + servoDriver->write(stopUs + stepSpeed * direction); lastCommand = SHUTTER_COMMAND_OPEN; - lastCommandTs = millis(); + lastTickMs = millis(); } } void close() override { - if (lastCommand != SHUTTER_COMMAND_NONE) { + if (lastCommand != SHUTTER_COMMAND_NONE || revolutions == 0) { stopRequested = true; } else { - servoDriver->write(stopUs - stepSpeed); + servoDriver->write(stopUs - stepSpeed * direction); lastCommand = SHUTTER_COMMAND_CLOSE; - lastCommandTs = millis(); + lastTickMs = millis(); } } void level(float level) override { - float levelDiff = (level / 100.f) - currentLevel; + targetLevel = round((level / 100.f) * revolutionsMax) / revolutionsMax; + float levelDiff = targetLevel - currentLevel; if (levelDiff < 0) { - stopTime = millis() - (totalTimeSpanMs * levelDiff); close(); } else if (levelDiff > 0) { - stopTime = millis() + (totalTimeSpanMs * levelDiff); open(); } } void loop() override { - long elapsed = millis() - lastCommandTs; - float percent = ((float)elapsed / totalTimeSpanMs); - if (stopRequested || (stopTime != 0 && millis() >= stopTime)) { + if (lastCommand != SHUTTER_COMMAND_NONE && (millis() - lastTickMs > 1000)) { + + // MOTOR ERROR!!!!! DID NOT RECEIVE "PULSE" for 1000ms + + String err = "No pulse received from motor."; + Logger::info("@%s [%s %s]", SHUTTER_CONTROL_NS_PREFIX, (IOEventPaths::Status_Error), err.c_str()); + eventSender->sendEvent((const uint8_t*)(IOEventPaths::Status_Error), &err, IOEventDataType::Text); + + stopRequested = true; + + // TODO: SHOULD GO BACK TO THE POSITION WHERE STARTED THE COMMAND + } + + if (stopRequested) { stopRequested = false; - stopTime = 0; stop(); - if (lastCommand == SHUTTER_COMMAND_OPEN) { - currentLevel += percent; - } else if (lastCommand == SHUTTER_COMMAND_CLOSE) { - currentLevel -= percent; - } + currentLevel = (float)revolutions / (float)revolutionsMax; lastCommand = SHUTTER_COMMAND_NONE; + targetLevel = -1; Logger::info("@%s [%s %.2f]", SHUTTER_CONTROL_NS_PREFIX, (IOEventPaths::Status_Level), currentLevel); - eventSender->sendEvent(domain.c_str(), address.c_str(), (const uint8_t*)(IOEventPaths::Status_Level), ¤tLevel, IOEventDataType::Float); + eventSender->sendEvent((const uint8_t*)(IOEventPaths::Status_Level), ¤tLevel, IOEventDataType::Float); } else if (lastCommand == SHUTTER_COMMAND_OPEN) { - if (elapsed > (totalTimeSpanMs - (currentLevel * totalTimeSpanMs))) { - lastCommand = SHUTTER_COMMAND_NONE; - stop(); - currentLevel = 1; - Logger::info("@%s [%s %.2f]", SHUTTER_CONTROL_NS_PREFIX, (IOEventPaths::Status_Level), currentLevel); - eventSender->sendEvent(domain.c_str(), address.c_str(), (const uint8_t *) (IOEventPaths::Status_Level), - ¤tLevel, IOEventDataType::Float); - } else if (millis() - lastEventMs > EVENT_EMIT_FREQUENCY) { - float level = currentLevel + percent; + if (millis() - lastEventMs > EVENT_EMIT_FREQUENCY) { + float level = (float)revolutions / (float)revolutionsMax; + lastEventMs = millis(); Logger::info("@%s [%s %.2f]", SHUTTER_CONTROL_NS_PREFIX, (IOEventPaths::Status_Level), level); - eventSender->sendEvent(domain.c_str(), address.c_str(), (const uint8_t *) (IOEventPaths::Status_Level), - &level, IOEventDataType::Float); + eventSender->sendEvent((const uint8_t *) (IOEventPaths::Status_Level), &level, IOEventDataType::Float); } } else if (lastCommand == SHUTTER_COMMAND_CLOSE) { - if (elapsed > (currentLevel * totalTimeSpanMs)) { - lastCommand = SHUTTER_COMMAND_NONE; - stop(); - currentLevel = 0; - Logger::info("@%s [%s %.2f]", SHUTTER_CONTROL_NS_PREFIX, (IOEventPaths::Status_Level), currentLevel); - eventSender->sendEvent(domain.c_str(), address.c_str(), (const uint8_t*)(IOEventPaths::Status_Level), ¤tLevel, IOEventDataType::Float); - } else if (millis() - lastEventMs > EVENT_EMIT_FREQUENCY) { - float level = currentLevel - percent; + if (millis() - lastEventMs > EVENT_EMIT_FREQUENCY) { + float level = (float)revolutions / (float)revolutionsMax; + lastEventMs = millis(); Logger::info("@%s [%s %.2f]", SHUTTER_CONTROL_NS_PREFIX, (IOEventPaths::Status_Level), level); - eventSender->sendEvent(domain.c_str(), address.c_str(), (const uint8_t *) (IOEventPaths::Status_Level), - &level, IOEventDataType::Float); + eventSender->sendEvent((const uint8_t *) (IOEventPaths::Status_Level), &level, IOEventDataType::Float); } } diff --git a/examples/shutter/shutter.cpp b/examples/shutter/shutter.cpp index 6b064bc..5502de0 100644 --- a/examples/shutter/shutter.cpp +++ b/examples/shutter/shutter.cpp @@ -27,27 +27,67 @@ * */ -#include - #include "configuration.h" +#include + #include "api/ShutterHandler.h" +#ifdef ESP32_C3 +#include +#endif + using namespace IO; using namespace Service; HomeGenie* homeGenie; + +#ifdef ESP32_C3 +using namespace Service::API::devices; +#include +// Custom status led (builtin NeoPixel RGB on pin 10) +Adafruit_NeoPixel pixels(1, CONFIG_StatusLedNeoPixelPin, NEO_GRB + NEO_KHZ800); +void statusLedCallback(bool isLedOn) { + if (isLedOn) { + pixels.setPixelColor(0, Adafruit_NeoPixel::Color(50, 50, 0)); + } else { + pixels.setPixelColor(0, Adafruit_NeoPixel::Color(0, 0, 0)); + } + pixels.show(); +} +unsigned long helloWorldDuration = 10000; +bool helloWorldActive = true; +#endif + + void setup() { +#ifdef ESP32_C3 + // Custom status led (builtin NeoPixel RGB on pin 10) +// if (!Config::isDeviceConfigured()) { + Config::statusLedCallback(&statusLedCallback); +// } + pixels.begin(); +#endif homeGenie = HomeGenie::getInstance(); //auto miniModule = homeGenie->getDefaultModule(); auto shutterControl = new ShutterControl(); + auto shutterHandler = new ShutterHandler(shutterControl); homeGenie->addIOHandler(shutterControl); - homeGenie->addAPIHandler(new ShutterHandler(shutterControl)); + homeGenie->addAPIHandler(shutterHandler); + +#ifdef ESP32_C3 + auto colorLight = new ColorLight(IO::IOEventDomains::HomeAutomation_HomeGenie, "C1", "Demo Light"); + colorLight->onSetColor([](float r, float g, float b) { + pixels.setPixelColor(0, r, g, b); + pixels.show(); + }); + homeGenie->addAPIHandler(colorLight); +#endif homeGenie->begin(); @@ -56,5 +96,10 @@ void setup() { void loop() { homeGenie->loop(); - +#ifdef ESP32_C3 + if (helloWorldActive && millis() > helloWorldDuration && Config::isDeviceConfigured()) { + helloWorldActive = false; + Config::statusLedCallback(nullptr); + } +#endif } diff --git a/examples/smart-sensor-display/configuration.h b/examples/smart-sensor-display/configuration.h index cceae9f..eb46e37 100644 --- a/examples/smart-sensor-display/configuration.h +++ b/examples/smart-sensor-display/configuration.h @@ -14,3 +14,5 @@ #define CONFIG_LightSensorPin 34 #endif + +#define CONFIG_MotionSensorPin 16 diff --git a/examples/smart-sensor-display/smart-sensor-display.cpp b/examples/smart-sensor-display/smart-sensor-display.cpp index 32988fb..6b202d2 100644 --- a/examples/smart-sensor-display/smart-sensor-display.cpp +++ b/examples/smart-sensor-display/smart-sensor-display.cpp @@ -68,7 +68,7 @@ Dashboard* dashboard; void setup() { //uint8_t batterySensorPin = 1; - uint8_t motionSensorPin = 16; + uint8_t motionSensorPin = CONFIG_MotionSensorPin; PowerManager::setWakeUpGPIO((gpio_num_t)motionSensorPin); diff --git a/platformio.ini b/platformio.ini index 240bc8d..ea02112 100644 --- a/platformio.ini +++ b/platformio.ini @@ -159,7 +159,7 @@ lib_deps = ${env.lib_deps} [env:color-light] platform = espressif32@6.5.0 -build_flags = -Os -I examples -I src -D CONFIG_StatusLedNeoPixelPin=10 +build_flags = -Os -I examples -I src build_src_filter = + - + board_build.flash_size = 4MB board_build.partitions = min_spiffs.csv @@ -174,7 +174,7 @@ board = esp32-c3-devkitc-02 #board_build.mcu = esp32c3 ; change MCU frequency #board_build.f_cpu = 160000000L -build_flags = -Os -I examples -I src -D ESP32_C3 -D CONFIG_StatusLedPin=-1 -D CONFIG_StatusLedNeoPixelPin=10 -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1 +build_flags = -Os -I examples -I src -D ESP32_C3 -D CONFIG_StatusLedPin=-1 -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1 build_src_filter = + - + board_build.flash_size = 4MB board_build.partitions = min_spiffs.csv @@ -192,6 +192,23 @@ lib_deps = ${env.lib_deps} arduino-libraries/Stepper@^1.1.3 madhephaestus/ESP32Servo@^1.1.1 +[env:shutter-c3] +platform = espressif32@6.5.0 +board = esp32-c3-devkitc-02 +#board = esp32-c3-devkitm-1 +; change microcontroller +#board_build.mcu = esp32c3 +; change MCU frequency +#board_build.f_cpu = 160000000L +build_flags = -Os -I examples -I src -D ESP32_C3 -D CONFIG_StatusLedPin=-1 -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1 +build_src_filter = + - + +board_build.flash_size = 4MB +board_build.partitions = min_spiffs.csv +lib_deps = ${env.lib_deps} + arduino-libraries/Stepper@^1.1.3 + madhephaestus/ESP32Servo@^1.1.1 + https://github.com/adafruit/Adafruit_NeoPixel@1.12.0 + [env:playground] platform = espressif32@6.5.0 diff --git a/src/io/IOEventPaths.h b/src/io/IOEventPaths.h index 4ec2263..82c84aa 100644 --- a/src/io/IOEventPaths.h +++ b/src/io/IOEventPaths.h @@ -41,6 +41,7 @@ namespace IO { const char Sensor_Humidity[] PROGMEM = "Sensor.Humidity"; const char Sensor_MotionDetect[] PROGMEM = "Sensor.MotionDetect"; const char Status_Battery[] PROGMEM = "Status.Battery"; + const char Status_Error[] PROGMEM = "Status.Error"; const char System_BytesFree[] PROGMEM = "System.BytesFree"; } } diff --git a/src/service/api/devices/ColorLight.cpp b/src/service/api/devices/ColorLight.cpp new file mode 100644 index 0000000..f0eaba9 --- /dev/null +++ b/src/service/api/devices/ColorLight.cpp @@ -0,0 +1,104 @@ +/* + * HomeGenie-Mini (c) 2018-2024 G-Labs + * + * + * This file is part of HomeGenie-Mini (HGM). + * + * HomeGenie-Mini is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HomeGenie-Mini is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HomeGenie-Mini. If not, see . + * + * + * Authors: + * - Generoso Martello + * + */ + +#include "ColorLight.h" + +namespace Service { namespace API { namespace devices { + + ColorLight::ColorLight(const char* domain, const char* address, const char* name): Dimmer(domain, address, name) { + module->type = "Color"; + + // add properties + auto propStatusColorHsb = new ModuleParameter(IOEventPaths::Status_ColorHsb); + propStatusColorHsb->setValue("1,1,0,.5"); + module->properties.add(propStatusColorHsb); + + onSetLevel([this](float l) { + color.setColor(color.getHue(), color.getSaturation(), l, defaultTransitionMs); + }); + } + + void ColorLight::loop() { + Dimmer::loop(); // parent + + if (color.isAnimating) { + if (setColorCallback != nullptr) { + setColorCallback(color.getRed(), color.getGreen(), color.getBlue()); + } + } + + } + + bool ColorLight::handleRequest(APIRequest* command, ResponseCallback* responseCallback) { + if (Dimmer::handleRequest(command, responseCallback)) return true; + + auto m = getModule(command->Domain.c_str(), command->Address.c_str()); + if (m != nullptr && command->Command == ControlApi::Control_ColorHsb) { + + auto hsvString = command->getOption(0); + + float o[4]; int oi = 0; + int ci; + do { + ci = hsvString.indexOf(","); + if (ci <= 0) { + o[oi] = hsvString.toFloat(); + break; + } + o[oi++] = hsvString.substring(0, ci).toFloat(); + hsvString = hsvString.substring(ci + 1); + } while (oi < 4); + + + color.setColor(o[0], o[1], o[2], o[3]*1000); + + + // Event Stream Message Enqueue (for MQTT/SSE/WebSocket propagation) + auto eventValue = command->getOption(0); + auto msg = QueuedMessage(m, IOEventPaths::Status_ColorHsb, eventValue, nullptr, IOEventDataType::Undefined); + m->setProperty(IOEventPaths::Status_ColorHsb, eventValue, nullptr, IOEventDataType::Undefined); + HomeGenie::getInstance()->getEventRouter().signalEvent(msg); + // level prop + auto levelValue = String(o[2]); // TODO: use sprintf %.6f + auto msg2 = QueuedMessage(m, IOEventPaths::Status_Level, levelValue, nullptr, IOEventDataType::Undefined); + m->setProperty(IOEventPaths::Status_Level, levelValue, nullptr, IOEventDataType::Undefined); + HomeGenie::getInstance()->getEventRouter().signalEvent(msg2); + + if (o[2] > 0) { + Switch::status = SWITCH_STATUS_ON; + Switch::onLevel = o[2]; + } else { + Switch::status = SWITCH_STATUS_OFF; + } + + responseCallback->writeAll(R"({ "ResponseText": "OK" })"); + return true; + + } + // not handled + return false; + } + +}}} \ No newline at end of file diff --git a/src/service/api/devices/ColorLight.h b/src/service/api/devices/ColorLight.h new file mode 100644 index 0000000..e42aa1b --- /dev/null +++ b/src/service/api/devices/ColorLight.h @@ -0,0 +1,117 @@ +/* + * HomeGenie-Mini (c) 2018-2024 G-Labs + * + * + * This file is part of HomeGenie-Mini (HGM). + * + * HomeGenie-Mini is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HomeGenie-Mini is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HomeGenie-Mini. If not, see . + * + * + * Authors: + * - Generoso Martello + * + */ + +#ifndef HOMEGENIE_MINI_COLORLIGHT_H +#define HOMEGENIE_MINI_COLORLIGHT_H + +#include +#include + +#include "Dimmer.h" + +namespace Service { namespace API { namespace devices { + + class LightColor { + public: + unsigned long duration; + bool isAnimating = false; + void setColor(float hue, float saturation, float value, unsigned long transitionMs) { + duration = transitionMs; + oh = h; + os = s; + ov = v; + h = hue; + s = saturation; + v = value; + startTime = millis(); + isAnimating = true; + } + float getProgress() { + float p = (float)(millis() - startTime) / (float)duration; + if (p >= 1) { + isAnimating = false; + p = 1; + } + return p; + } + float getHue() { + return oh + ((h - oh) * getProgress()); + } + float getSaturation() { + return os + ((s - os) * getProgress()); + } + float getValue() { + return ov + ((v - ov) * getProgress()); + } + float getRed() { + auto orgb = Utility::hsv2rgb(hfix(oh), os, ov); + auto crgb = Utility::hsv2rgb(hfix(h), s, v); + float r = orgb.r + ((crgb.r - orgb.r) * getProgress()); + return r; + } + float getGreen() { + auto orgb = Utility::hsv2rgb(hfix(oh), os, ov); + auto crgb = Utility::hsv2rgb(hfix(h), s, v); + float g = orgb.g + ((crgb.g - orgb.g) * getProgress()); + return g; + } + float getBlue() { + auto orgb = Utility::hsv2rgb(hfix(oh), os, ov); + auto crgb = Utility::hsv2rgb(hfix(h), s, v); + float b = orgb.b + ((crgb.b - orgb.b) * getProgress()); + return b; + } + private: + float h; + float s; + float v; + float oh, os, ov; + unsigned long startTime; + float hfix(float h) { + return 1.325f - h; + } + + }; + + class ColorLight: public Dimmer { + public: + ColorLight(const char* domain, const char* address, const char* name); + + void loop() override; + bool handleRequest(APIRequest*, ResponseCallback*) override; + + void onSetColor(std::function callback) { + setColorCallback = std::move(callback); + } + private: + LightColor color; + std::function setColorCallback = nullptr; + + void setColor(float h, float s, float v, float duration); + }; + +}}} + +#endif //HOMEGENIE_MINI_COLORLIGHT_H diff --git a/src/service/api/devices/Dimmer.cpp b/src/service/api/devices/Dimmer.cpp new file mode 100644 index 0000000..0a217f7 --- /dev/null +++ b/src/service/api/devices/Dimmer.cpp @@ -0,0 +1,81 @@ +/* + * HomeGenie-Mini (c) 2018-2024 G-Labs + * + * + * This file is part of HomeGenie-Mini (HGM). + * + * HomeGenie-Mini is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HomeGenie-Mini is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HomeGenie-Mini. If not, see . + * + * + * Authors: + * - Generoso Martello + * + */ + +#include "Dimmer.h" + +namespace Service { namespace API { namespace devices { + + Dimmer::Dimmer(const char* domain, const char* address, const char* name): Switch(domain, address, name) { + setLoopInterval(10); // fixed transition frequency + module->type = "Dimmer"; + onSetStatus([this](SwitchStatus status) { + level.setLevel(status == SWITCH_STATUS_ON ? 1 : 0, defaultTransitionMs); + }); + } + + void Dimmer::loop() { + + if (level.isAnimating) { + if (setLevelCallback != nullptr) { + setLevelCallback(level.getLevel()); + } + } + + } + + bool Dimmer::handleRequest(APIRequest *command, ResponseCallback* responseCallback) { + if (Switch::handleRequest(command, responseCallback)) return true; + + auto m = getModule(command->Domain.c_str(), command->Address.c_str()); + if (m != nullptr && command->Command == ControlApi::Control_Level) { + + auto l = command->getOption(0).toFloat() / 100.0F; // 0.00 - 1.00 0 = OFF, 1.00 = MAX + auto transition = command->getOption(1).isEmpty() ? defaultTransitionMs : command->getOption(1).toFloat(); // ms + + level.setLevel(l, transition); + + // Event Stream Message Enqueue (for MQTT/SSE/WebSocket propagation) + auto eventPath = IOEventPaths::Status_Level; + auto eventValue = String(l); + auto msg = QueuedMessage(m, eventPath, eventValue, &l, IOEventDataType::Float); + m->setProperty(eventPath, eventValue, &l, IOEventDataType::Float); + HomeGenie::getInstance()->getEventRouter().signalEvent(msg); + + if (l > 0) { + Switch::status = SWITCH_STATUS_ON; + Switch::onLevel = l; + } else { + Switch::status = SWITCH_STATUS_OFF; + } + + responseCallback->writeAll(R"({ "ResponseText": "OK" })"); + return true; + + } + // not handled + return false; + } + +}}} \ No newline at end of file diff --git a/src/service/api/devices/Dimmer.h b/src/service/api/devices/Dimmer.h new file mode 100644 index 0000000..ec57e59 --- /dev/null +++ b/src/service/api/devices/Dimmer.h @@ -0,0 +1,83 @@ +/* + * HomeGenie-Mini (c) 2018-2024 G-Labs + * + * + * This file is part of HomeGenie-Mini (HGM). + * + * HomeGenie-Mini is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HomeGenie-Mini is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HomeGenie-Mini. If not, see . + * + * + * Authors: + * - Generoso Martello + * + */ + +#ifndef HOMEGENIE_MINI_DIMMER_H +#define HOMEGENIE_MINI_DIMMER_H + +#include + +#include "Switch.h" + +namespace Service { namespace API { namespace devices { + + class DimmerLevel { + public: + unsigned long duration; + bool isAnimating = false; + void setLevel(float l, unsigned long transitionMs) { + duration = transitionMs; + ol = level; + level = l; + startTime = millis(); + isAnimating = true; + } + float getProgress() { + float p = (float)(millis() - startTime) / (float)duration; + if (p >= 1) { + isAnimating = false; + p = 1; + } + return p; + } + float getLevel() { + return ol + ((level - ol) * getProgress()); + } + private: + float level; + float ol; + unsigned long startTime; + + }; + + class Dimmer: public Switch { + public: + Dimmer(const char* domain, const char* address, const char* name); + void loop() override; + + bool handleRequest(APIRequest*, ResponseCallback*) override; + + void onSetLevel(std::function callback) { + setLevelCallback = std::move(callback); + } + private: + DimmerLevel level; + std::function setLevelCallback = nullptr; + protected: + const unsigned long defaultTransitionMs = 500; + }; + +}}} + +#endif //HOMEGENIE_MINI_DIMMER_H diff --git a/src/service/api/devices/Switch.cpp b/src/service/api/devices/Switch.cpp new file mode 100644 index 0000000..90f813f --- /dev/null +++ b/src/service/api/devices/Switch.cpp @@ -0,0 +1,108 @@ +/* + * HomeGenie-Mini (c) 2018-2024 G-Labs + * + * + * This file is part of HomeGenie-Mini (HGM). + * + * HomeGenie-Mini is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HomeGenie-Mini is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HomeGenie-Mini. If not, see . + * + * + * Authors: + * - Generoso Martello + * + */ + +#include "Switch.h" + +namespace Service { namespace API { namespace devices { + + Switch::Switch(const char* domain, const char* address, const char* name) { + module = new Module(); + module->domain = domain; + module->address = address; + module->type = "Switch"; + module->name = name; + // add properties + auto propStatusLevel = new ModuleParameter(IOEventPaths::Status_Level); + propStatusLevel->value = "0"; + module->properties.add(propStatusLevel); + + moduleList.add(module); + } + + void Switch::init() { + } + + bool Switch::handleRequest(APIRequest *command, ResponseCallback* responseCallback) { + auto m = getModule(command->Domain.c_str(), command->Address.c_str()); + if (m != nullptr) { + + auto eventPath = IOEventPaths::Status_Level; + SwitchStatus s = SWITCH_STATUS_NOT_SET; + if (command->Command == ControlApi::Control_On) { + s = SWITCH_STATUS_ON; + } else if (command->Command == ControlApi::Control_Off) { + s = SWITCH_STATUS_OFF; + } else if (command->Command == ControlApi::Control_Toggle) { + s = status == SWITCH_STATUS_ON ? SWITCH_STATUS_OFF : SWITCH_STATUS_ON; + } + + if (s != SWITCH_STATUS_NOT_SET) { + status = s; + + if (setStatusCallback != nullptr) { + setStatusCallback(status); + } + + // Event Stream Message Enqueue (for MQTT/SSE/WebSocket propagation) + float l = status == SWITCH_STATUS_ON ? onLevel : 0; + auto eventValue = String(l); + auto msg = QueuedMessage(m, eventPath, eventValue, &l, IOEventDataType::Float); + m->setProperty(eventPath, eventValue, &l, IOEventDataType::Float); + HomeGenie::getInstance()->getEventRouter().signalEvent(msg); + + responseCallback->writeAll(R"({ "ResponseText": "OK" })"); + + return true; + } + + } + // not handled + return false; + } + + bool Switch::canHandleDomain(String* domain) { + return domain->equals(IO::IOEventDomains::HomeAutomation_HomeGenie); + } + + bool Switch::handleEvent(IIOEventSender *sender, + const char* domain, const char* address, + const unsigned char *eventPath, void *eventData, IOEventDataType dataType) { + return false; + } + + Module* Switch::getModule(const char* domain, const char* address) { + for (int i = 0; i < moduleList.size(); i++) { + Module* m = moduleList.get(i); + if (m->domain.equals(domain) && m->address.equals(address)) + return m; + } + return nullptr; + } + + LinkedList* Switch::getModuleList() { + return &moduleList; + } + +}}} diff --git a/src/service/api/devices/Switch.h b/src/service/api/devices/Switch.h new file mode 100644 index 0000000..f94b38c --- /dev/null +++ b/src/service/api/devices/Switch.h @@ -0,0 +1,65 @@ +/* + * HomeGenie-Mini (c) 2018-2024 G-Labs + * + * + * This file is part of HomeGenie-Mini (HGM). + * + * HomeGenie-Mini is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HomeGenie-Mini is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HomeGenie-Mini. If not, see . + * + * + * Authors: + * - Generoso Martello + * + */ + +#ifndef HOMEGENIE_MINI_SWITCH_H +#define HOMEGENIE_MINI_SWITCH_H + +#include + +namespace Service { namespace API { namespace devices { + + enum SwitchStatus { + SWITCH_STATUS_NOT_SET = -1, + SWITCH_STATUS_OFF, + SWITCH_STATUS_ON, + }; + + class Switch: public Task, public APIHandler { + public: + Switch(const char* domain, const char* address, const char* name); + void init() override; + bool canHandleDomain(String* domain) override; + bool handleRequest(APIRequest*, ResponseCallback*) override; + bool handleEvent(IIOEventSender*, + const char* domain, const char* address, + const unsigned char *eventPath, void* eventData, IOEventDataType) override; + + Module* getModule(const char* domain, const char* address) override; + LinkedList* getModuleList() override; + void onSetStatus(std::function callback) { + setStatusCallback = std::move(callback); + } + private: + LinkedList moduleList; + std::function setStatusCallback = nullptr; + protected: + Module* module; + SwitchStatus status = SWITCH_STATUS_NOT_SET; + float onLevel = 1; + }; + +}}} + +#endif //HOMEGENIE_MINI_SWITCH_H