diff --git a/include/mqtt.h b/include/mqtt.h index 3d298d0..56bc71b 100644 --- a/include/mqtt.h +++ b/include/mqtt.h @@ -7,4 +7,5 @@ static PubSubClient mqtt(wifi); void mqtt_setup(); -void mqtt_loop(); \ No newline at end of file +void mqtt_loop(); +void mqtt_publish_current_state(String state); \ No newline at end of file diff --git a/include/state.h b/include/state.h new file mode 100644 index 0000000..6b5bacc --- /dev/null +++ b/include/state.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include "prototypes.h" + +class State { + AnimationMode _mode = AM_NONE; + uint16_t _duration = 0; + int16_t _brightness = -1; + uint8_t _speedup = 0; + CRGB _color = CRGB::Black; + + public: + void parse_json(char* str); + void set(String key, String value); + void commit(); + static void publish_current_state(); + + void parse_state(String state); + void parse_mode(String mode); + void set_mode(AnimationMode mode); + void set_duration(uint16_t duration); + void set_brightness(uint8_t brightness); + void set_color(JsonObject rgb); + void set_speedup(uint8_t speedup); +}; \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index f540b6d..46bc738 100644 --- a/platformio.ini +++ b/platformio.ini @@ -20,4 +20,5 @@ monitor_port = /dev/cu.wchusbserial* monitor_speed = 74880 monitor_filters = default, time, send_on_enter, esp8266_exception_decoder lib_deps = fastled/FastLED @ 3.4.0 - PubSubClient @ 2.8 \ No newline at end of file + PubSubClient @ 2.8 + ArduinoJson \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 086ad51..6f8961d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include "corner.h" #include "prototypes.h" #include "mqtt.h" +#include "state.h" std::vector nodes; std::list edges; @@ -205,6 +206,7 @@ void loop() { FastLED.setBrightness(return_to_brightness); return_to_brightness = -1; } + State::publish_current_state(); } } diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 31b589e..6d63da0 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -1,16 +1,25 @@ #include "mqtt.h" #include "tools.h" #include "prototypes.h" +#include "state.h" void connect() { LOGln("Connecting to MQTT broker..."); - if (mqtt.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS, MQTT_TOPIC_STATE, 0, true, "OFFLINE")) { + if (mqtt.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS, MQTT_TOPIC "state", 0, true, "OFFLINE")) { LOGln("Connected."); - mqtt.publish(MQTT_TOPIC_STATE, "ONLINE", true); + mqtt.publish(MQTT_TOPIC "available", "online", true); char buffer[40]; - snprintf(buffer, 40, "ONLINE %s", wifi.localIP().toString().c_str()); - mqtt.publish(MQTT_TOPIC_STATE_LONG, buffer, true); - mqtt.subscribe(MQTT_TOPIC_COMMANDS); + snprintf(buffer, 40, "online %s", wifi.localIP().toString().c_str()); + mqtt.publish(MQTT_TOPIC "available_long", buffer, true); + mqtt.subscribe(MQTT_TOPIC"cmnd"); + String discovery_msg = "{\"" + "\"~\":\"" MQTT_TOPIC ",\"avty_t\":\"~available\",\"cmd_t\":\"~cmnd\",\"stat_t\":\"~state\"," + "\"name\":\"ESPleaf\",\"schema\":\"json\"," + "\"brightness\":true,\"bri_scl\":255," + "\"effect\":true,\"effect_list\":[\"off\",\"corners\",\"nodes\",\"flash\",\"static\"]," + "\"rgb\":true}"; + + mqtt.publish("homeassistant/light/" HOMEASSISTANT_OBJECT_ID "/config", discovery_msg.c_str(), true); } else { LOGln("Connection failed. Reason: %d", mqtt.state()); delay(1000); @@ -19,70 +28,34 @@ LOGln("Connecting to MQTT broker..."); void callback(char* topic, byte* pl, unsigned int length) { pl[length] = 0; - String payload((char*)pl); - // Syntax: key=value&key2=value2... - uint16_t duration = 0; - AnimationMode new_mode = AM_NONE; - String current_part; - LOGln("Received command %s", payload.c_str()); - while (payload.length() > 0) { - int offset = payload.indexOf("&"); - if (offset != -1) { - current_part = payload.substring(0, offset); - payload = payload.substring(offset + 1); - } else { - current_part = payload; - payload = ""; - } - offset = current_part.indexOf("="); - if (offset==-1) { - LOGln("Parameter without '=' detected: %s", current_part.c_str()); - continue; - } - String key = current_part.substring(0, offset); - String value = current_part.substring(offset+1); - LOGln(" Processing key %s with value %s", key.c_str(), value.c_str()); - - if (key.equals("mode")) { - if (value.equals("nodes")) { new_mode = AM_NODES; } - else if (value.equals("first_node")) { new_mode = AM_FIRST_NODE; } - else if (value.equals("corners")) { new_mode = AM_CORNERS; } - else if (value.equals("first_corner")) { new_mode = AM_FIRST_CORNER; } - else if (value.equals("off")) { new_mode = AM_OFF; } - else if (value.equals("flash")) { new_mode = AM_FLASH; } - else if (value.equals("static")) { new_mode = AM_STATIC; } - else { LOGln("Unknown mode '%s'.", value.c_str()); } - } else if (key.equals("duration")) { - duration = value.toInt(); - } else if (key.equals("brightness")) { - if (temp_mode_until == 0) { - return_to_brightness = FastLED.getBrightness(); + State s; + if (pl[0]=='{') { + s.parse_json((char*)pl); + } else { + String payload((char*)pl); + // Syntax: key=value&key2=value2... + String current_part; + LOGln("Received command %s", payload.c_str()); + while (payload.length() > 0) { + int offset = payload.indexOf("&"); + if (offset != -1) { + current_part = payload.substring(0, offset); + payload = payload.substring(offset + 1); + } else { + current_part = payload; + payload = ""; } - FastLED.setBrightness(value.toInt()); - } else if (key.equals("color")) { - if (value.equals("red")) { color = CRGB::Red; } - else if (value.equals("green")) { color = CRGB::Green; } - else if (value.equals("blue")) { color = CRGB::Blue; } - else if (value.equals("pink")) { color = CRGB::Pink; } - else if (value.equals("yellow")) { color = CRGB::Yellow; } - else if (value.equals("orange")) { color = CRGB::Orange; } - else { LOGln("Unknown color name %s.", value.c_str());} - } else if (key.equals("speedup")) { - speedup = value.toInt(); - } else { - LOGln("Unknown key '%s'. (For reference: Value is '%s'.)", key.c_str(), value.c_str()); - } - } - LOGln("Finished processing the command."); - - if (new_mode != AM_NONE) { - if (duration > 0) { - temp_mode = new_mode; - temp_mode_until = millis() + duration*1000; - } else { - mode = new_mode; + offset = current_part.indexOf("="); + if (offset==-1) { + LOGln("Parameter without '=' detected: %s", current_part.c_str()); + continue; + } + String key = current_part.substring(0, offset); + String value = current_part.substring(offset+1); + s.set(key, value); } } + s.commit(); } void mqtt_setup() { @@ -97,4 +70,8 @@ void mqtt_loop() { connect(); } mqtt.loop(); -} \ No newline at end of file +} + +void mqtt_publish_current_state(String state) { + mqtt.publish(MQTT_TOPIC "state", state.c_str(), true); +}; \ No newline at end of file diff --git a/src/state.cpp b/src/state.cpp new file mode 100644 index 0000000..93c6566 --- /dev/null +++ b/src/state.cpp @@ -0,0 +1,117 @@ +#include "state.h" +#include "tools.h" +#include "mqtt.h" + +#include + +void State::parse_json(char* str) { + StaticJsonDocument<512> json; + deserializeJson(json, str); + + if(json.containsKey("state")) parse_state(json["state"]); + if(json.containsKey("effect")) parse_mode(json["effect"]); + if(json.containsKey("duration")) set_duration(json["duration"]); + if(json.containsKey("brightness")) set_brightness(json["brightness"]); + if(json.containsKey("rgb")) set_color(json["rgb"]); + if(json.containsKey("speedup")) set_speedup(json["speedup"]); +} + +void State::parse_state(String state) { + if (state.equals("ON")) { + set_mode(AM_CORNERS); + } else if (state.equals("OFF")) { + set_mode(AM_OFF); + } else { + LOGln("parse_state: Unknown state %s", state.c_str()); + } +} + +void State::parse_mode(String mode) { + if (mode.equals("nodes")) { set_mode(AM_NODES); } + else if (mode.equals("first_node")) { set_mode(AM_FIRST_NODE); } + else if (mode.equals("corners")) { set_mode(AM_CORNERS); } + else if (mode.equals("first_corner")) { set_mode(AM_FIRST_CORNER); } + else if (mode.equals("off")) { set_mode(AM_OFF); } + else if (mode.equals("flash")) { set_mode(AM_FLASH); } + else if (mode.equals("static")) { set_mode(AM_STATIC); } + else { LOGln("parse_mode: Unknown mode '%s'.", mode.c_str()); } +} + +void State::set_mode(AnimationMode m) { + _mode = m; +} + +void State::set_duration(uint16_t d) { + _duration = d; +} + +void State::set_brightness(uint8_t b) { + _brightness = b; +} + +void State::set_color(JsonObject rgb) { + if (!rgb.containsKey("r") || !rgb.containsKey("g") || !rgb.containsKey("b")) { + LOGln("set_color: Invalid rgb data."); + } else { + _color = CRGB(rgb["r"], rgb["g"], rgb["b"]); + } +} + +void State::set_speedup(uint8_t s) { + _speedup = s; +} + +void State::set(String key, String value) { + if (key.equals("state")) { parse_state(value); } + else if (key.equals("effect") || key.equals("mode")) { parse_mode(value); } + else if (key.equals("duration")) { set_duration(value.toInt()); } + else if (key.equals("brightness")) { set_brightness(value.toInt()); } + else if (key.equals("color")) { + if (value.equals("red")) { _color = CRGB::Red; } + else if (value.equals("green")) { _color = CRGB::Green; } + else if (value.equals("blue")) { _color = CRGB::Blue; } + else if (value.equals("pink")) { _color = CRGB::Pink; } + else if (value.equals("yellow")) { _color = CRGB::Yellow; } + else if (value.equals("orange")) { _color = CRGB::Orange; } + else { LOGln("Unknown color name %s.", value.c_str());} + } else if (key.equals("speedup")) { set_speedup(value.toInt()); } + else { + LOGln("Unknown key '%s'. (For reference: Value is '%s'.)", key.c_str(), value.c_str()); + } +} + +void State::commit() { + if (_brightness >= 0) { + if (temp_mode_until == 0) { + return_to_brightness = FastLED.getBrightness(); + } + FastLED.setBrightness(_brightness); + } + if (_mode != AM_NONE) { + if (_duration > 0) { + temp_mode = _mode; + temp_mode_until = millis() + _duration*1000; + } else { + mode = _mode; + } + } + if (_color != CRGB(0, 0, 0)) { + color = _color; + } + + publish_current_state(); +} + +void State::publish_current_state() { + StaticJsonDocument<512> json; + json["state"] = (mode==AM_OFF) ? "OFF" : "ON"; + json["brightness"] = FastLED.getBrightness(); + json["effect"] = mode; + JsonObject rgb = json.createNestedObject("rgb"); + rgb["r"] = color.r; + rgb["g"] = color.g; + rgb["b"] = color.b; + String result = ""; + serializeJson(json, result); + mqtt_publish_current_state(result); +} \ No newline at end of file