diff --git a/mqtt_esp8266/config-sample.h b/mqtt_esp8266/config-sample.h new file mode 100644 index 0000000..1b826c0 --- /dev/null +++ b/mqtt_esp8266/config-sample.h @@ -0,0 +1,55 @@ +/* + * This is a sample configuration file for the "mqtt_esp8266" light. + * + * Change the settings below and save the file as "config.h" + * You can then upload the code using the Arduino IDE. + */ + +// Define the strip type +enum strip { BRIGHTNESS // only one color + , RGB // RGB LEDs + , RGBW // RGB LEDs with an extra white LED per LED + }; +#define CONFIG_STRIP RGB + +// Pins +// In case of BRIGHTNESS: only RED is used +// In case of RGB(W): red, green, blue(, white) is used +// All values need to be present, if they are not needed, just write any value, +// it will be ignored. +#define CONFIG_PIN_RED 0 +#define CONFIG_PIN_GREEN 2 // only needed for RGB(W) strips +#define CONFIG_PIN_BLUE 3 // only needed for RGB(W) strips +#define CONFIG_PIN_WHITE 4 // only needed for RGBW strips + +// WiFi +#define CONFIG_WIFI_SSID "{WIFI-SSID}" +#define CONFIG_WIFI_PASS "{WIFI-PASSWORD}" + +// MQTT +#define CONFIG_MQTT_HOST "{MQTT-SERVER}" +#define CONFIG_MQTT_USER "{MQTT-USERNAME}" +#define CONFIG_MQTT_PASS "{MQTT-PASSWORD}" +#define CONFIG_MQTT_CLIENT_ID "ESP_LED" // Must be unique on the MQTT network + +// MQTT Topics +#define CONFIG_MQTT_TOPIC_STATE "home/ESP_LED" +#define CONFIG_MQTT_TOPIC_SET "home/ESP_LED/set" + +#define CONFIG_MQTT_PAYLOAD_ON "ON" +#define CONFIG_MQTT_PAYLOAD_OFF "OFF" + +// Miscellaneous +// Default number of flashes if no value was given +#define CONFIG_DEFAULT_FLASH_LENGTH 2 +// Number of seconds for one transition in colorfade mode +#define CONFIG_COLORFADE_TIME_SLOW 10 +#define CONFIG_COLORFADE_TIME_FAST 3 + +// Reverse the LED logic +// false: 0 (off) - 255 (bright) +// true: 255 (off) - 0 (bright) +#define CONFIG_INVERT_LED_LOGIC false + +// Enables Serial and print statements +#define CONFIG_DEBUG false diff --git a/mqtt_esp8266/mqtt_esp8266.ino b/mqtt_esp8266/mqtt_esp8266.ino new file mode 100644 index 0000000..2f00c56 --- /dev/null +++ b/mqtt_esp8266/mqtt_esp8266.ino @@ -0,0 +1,552 @@ +/* + * ESP8266 MQTT Lights for Home Assistant. + * + * This file is a joined file for Brightness, RBG and RGBW lights. + * + * See https://github.com/corbanmailloux/esp-mqtt-rgb-led + */ + +// Set configuration options for pins, WiFi, and MQTT in the following file: +#include "config.h" + +// https://github.com/bblanchon/ArduinoJson +#include + +#include + +// http://pubsubclient.knolleary.net/ +#include + +const bool rgb = CONFIG_STRIP == RGB || CONFIG_STRIP == RGBW; +const bool rgbw = CONFIG_STRIP == RGBW; +const bool debug_mode = CONFIG_DEBUG; +const bool led_invert = CONFIG_INVERT_LED_LOGIC; + +const int redPin = CONFIG_PIN_RED; +const int txPin = BUILTIN_LED; // On-board blue LED +const int greenPin = CONFIG_PIN_GREEN; +const int bluePin = CONFIG_PIN_BLUE; +const int whitePin = CONFIG_PIN_WHITE; + +const char* ssid = CONFIG_WIFI_SSID; +const char* password = CONFIG_WIFI_PASS; + +const char* mqtt_server = CONFIG_MQTT_HOST; +const char* mqtt_username = CONFIG_MQTT_USER; +const char* mqtt_password = CONFIG_MQTT_PASS; +const char* client_id = CONFIG_MQTT_CLIENT_ID; + +// Topics +const char* light_state_topic = CONFIG_MQTT_TOPIC_STATE; +const char* light_set_topic = CONFIG_MQTT_TOPIC_SET; + +const char* on_cmd = CONFIG_MQTT_PAYLOAD_ON; +const char* off_cmd = CONFIG_MQTT_PAYLOAD_OFF; + +const int BUFFER_SIZE = JSON_OBJECT_SIZE(20); + +// Maintained state for reporting to HA +byte red = 255; +byte green = 255; +byte blue = 255; +byte white = 255; +byte brightness = 255; + +// Real values to write to the LEDs (ex. including brightness and state) +byte realRed = 0; +byte realGreen = 0; +byte realBlue = 0; +byte realWhite = 0; + +bool stateOn = false; + +// Globals for fade/transitions +bool startFade = false; +unsigned long lastLoop = 0; +int transitionTime = 0; +bool inFade = false; +int loopCount = 0; +int stepR, stepG, stepB, stepW; +int redVal, grnVal, bluVal, whtVal; + +// Globals for flash +bool flash = false; +bool startFlash = false; +int flashLength = 0; +unsigned long flashStartTime = 0; +byte flashRed = red; +byte flashGreen = green; +byte flashBlue = blue; +byte flashWhite = white; +byte flashBrightness = brightness; + +// Globals for colorfade +bool colorfade = false; +int currentColor = 0; +// {red, grn, blu, wht} +const byte colors[][4] = { + {255, 0, 0, 0}, + {0, 255, 0, 0}, + {0, 0, 255, 0}, + {255, 80, 0, 0}, + {163, 0, 255, 0}, + {0, 255, 255, 0}, + {255, 255, 0, 0} +}; +const int numColors = 7; + +WiFiClient espClient; +PubSubClient client(espClient); + +void setup() { + pinMode(redPin, OUTPUT); + if (rgb) { + pinMode(greenPin, OUTPUT); + pinMode(bluePin, OUTPUT); + if (rgbw) { + pinMode(whitePin, OUTPUT); + } + } + + pinMode(txPin, OUTPUT); + digitalWrite(txPin, HIGH); // Turn off the on-board LED + + analogWriteRange(255); + + if (debug_mode) { + Serial.begin(115200); + } + + setup_wifi(); + client.setServer(mqtt_server, 1883); + client.setCallback(callback); +} + +void setup_wifi() { + + delay(10); + // We start by connecting to a WiFi network + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); +} + + /* + SAMPLE PAYLOAD (BRIGHTNESS): + { + "brightness": 120, + "flash": 2, + "transition": 5, + "state": "ON" + } + + SAMPLE PAYLOAD (RGB): + { + "brightness": 120, + "color": { + "r": 255, + "g": 100, + "b": 100 + }, + "white_value": 255, + "flash": 2, + "transition": 5, + "state": "ON", + "effect": "colorfade_fast" + } + */ +void callback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + + char message[length + 1]; + for (int i = 0; i < length; i++) { + message[i] = (char)payload[i]; + } + message[length] = '\0'; + Serial.println(message); + + if (!processJson(message)) { + return; + } + + if (stateOn) { + // Update lights + realRed = map(red, 0, 255, 0, brightness); + realGreen = map(green, 0, 255, 0, brightness); + realBlue = map(blue, 0, 255, 0, brightness); + realWhite = map(white, 0, 255, 0, brightness); + } + else { + realRed = 0; + realGreen = 0; + realBlue = 0; + realWhite = 0; + } + + startFade = true; + inFade = false; // Kill the current fade + + sendState(); +} + +bool processJson(char* message) { + StaticJsonBuffer jsonBuffer; + + JsonObject& root = jsonBuffer.parseObject(message); + + if (!root.success()) { + Serial.println("parseObject() failed"); + return false; + } + + if (root.containsKey("state")) { + if (strcmp(root["state"], on_cmd) == 0) { + stateOn = true; + } + else if (strcmp(root["state"], off_cmd) == 0) { + stateOn = false; + } + } + + // If "flash" is included, treat RGB and brightness differently + if (root.containsKey("flash") || + (root.containsKey("effect") && strcmp(root["effect"], "flash") == 0)) { + + if (root.containsKey("flash")) { + flashLength = (int)root["flash"] * 1000; + } + else { + flashLength = CONFIG_DEFAULT_FLASH_LENGTH * 1000; + } + + if (root.containsKey("brightness")) { + flashBrightness = root["brightness"]; + } + else { + flashBrightness = brightness; + } + + if (rgb && root.containsKey("color")) { + flashRed = root["color"]["r"]; + flashGreen = root["color"]["g"]; + flashBlue = root["color"]["b"]; + } + else { + flashRed = red; + flashGreen = green; + flashBlue = blue; + } + + if (rgbw && root.containsKey("white_value")) { + flashWhite = root["white_value"]; + } + else { + flashWhite = white; + } + + flashRed = map(flashRed, 0, 255, 0, flashBrightness); + flashGreen = map(flashGreen, 0, 255, 0, flashBrightness); + flashBlue = map(flashBlue, 0, 255, 0, flashBrightness); + flashWhite = map(flashWhite, 0, 255, 0, flashBrightness); + + flash = true; + startFlash = true; + } + else if (rgb && root.containsKey("effect") && + (strcmp(root["effect"], "colorfade_slow") == 0 || strcmp(root["effect"], "colorfade_fast") == 0)) { + flash = false; + colorfade = true; + currentColor = 0; + if (strcmp(root["effect"], "colorfade_slow") == 0) { + transitionTime = CONFIG_COLORFADE_TIME_SLOW; + } + else { + transitionTime = CONFIG_COLORFADE_TIME_FAST; + } + } + else if (colorfade && !root.containsKey("color") && root.containsKey("brightness")) { + // Adjust brightness during colorfade + // (will be applied when fading to the next color) + brightness = root["brightness"]; + } + else { // No effect + flash = false; + colorfade = false; + + if (rgb && root.containsKey("color")) { + red = root["color"]["r"]; + green = root["color"]["g"]; + blue = root["color"]["b"]; + } + + if (rgbw && root.containsKey("white_value")) { + white = root["white_value"]; + } + + if (root.containsKey("brightness")) { + brightness = root["brightness"]; + } + + if (root.containsKey("transition")) { + transitionTime = root["transition"]; + } + else { + transitionTime = 0; + } + } + + return true; +} + +void sendState() { + StaticJsonBuffer jsonBuffer; + + JsonObject& root = jsonBuffer.createObject(); + + root["state"] = (stateOn) ? on_cmd : off_cmd; + if (rgb) { + JsonObject& color = root.createNestedObject("color"); + color["r"] = red; + color["g"] = green; + color["b"] = blue; + } + + root["brightness"] = brightness; + + if (rgbw) { + root["white_value"] = white; + } + + if (rgb && colorfade) { + if (transitionTime == CONFIG_COLORFADE_TIME_SLOW) { + root["effect"] = "colorfade_slow"; + } + else { + root["effect"] = "colorfade_fast"; + } + } + else { + root["effect"] = "null"; + } + + char buffer[root.measureLength() + 1]; + root.printTo(buffer, sizeof(buffer)); + + client.publish(light_state_topic, buffer, true); +} + +void reconnect() { + // Loop until we're reconnected + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + // Attempt to connect + if (client.connect(client_id, mqtt_username, mqtt_password)) { + Serial.println("connected"); + client.subscribe(light_set_topic); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void setColor(int inR, int inG, int inB, int inW) { + if (led_invert) { + inR = (255 - inR); + inG = (255 - inG); + inB = (255 - inB); + inW = (255 - inW); + } + + analogWrite(redPin, inR); + if (rgb) { + analogWrite(greenPin, inG); + analogWrite(bluePin, inB); + if (rgbw) { + analogWrite(whitePin, inW); + } + } + + Serial.println("Setting LEDs:"); + Serial.print(rgb ? "r: " : ""); + Serial.print(inR); + if (rgb) { + Serial.print(", g: "); + Serial.print(inG); + Serial.print(", b: "); + Serial.print(inB); + if (rgbw) { + Serial.print(", w: "); + Serial.print(inW); + } + } + Serial.println(); +} + +void loop() { + + if (!client.connected()) { + reconnect(); + } + client.loop(); + + if (flash) { + if (startFlash) { + startFlash = false; + flashStartTime = millis(); + } + + if ((millis() - flashStartTime) <= flashLength) { + if ((millis() - flashStartTime) % 1000 <= 500) { + setColor(flashRed, flashGreen, flashBlue, flashWhite); + } + else { + setColor(0, 0, 0, 0); + // If you'd prefer the flashing to happen "on top of" + // the current color, uncomment the next line. + // setColor(realRed, realGreen, realBlue, realWhite); + } + } + else { + flash = false; + setColor(realRed, realGreen, realBlue, realWhite); + } + } + else if (rgb && colorfade && !inFade) { + realRed = map(colors[currentColor][0], 0, 255, 0, brightness); + realGreen = map(colors[currentColor][1], 0, 255, 0, brightness); + realBlue = map(colors[currentColor][2], 0, 255, 0, brightness); + realWhite = map(colors[currentColor][3], 0, 255, 0, brightness); + currentColor = (currentColor + 1) % numColors; + startFade = true; + } + + if (startFade) { + // If we don't want to fade, skip it. + if (transitionTime == 0) { + setColor(realRed, realGreen, realBlue, realWhite); + + redVal = realRed; + grnVal = realGreen; + bluVal = realBlue; + whtVal = realWhite; + + startFade = false; + } + else { + loopCount = 0; + stepR = calculateStep(redVal, realRed); + stepG = calculateStep(grnVal, realGreen); + stepB = calculateStep(bluVal, realBlue); + stepW = calculateStep(whtVal, realWhite); + + inFade = true; + } + } + + if (inFade) { + startFade = false; + unsigned long now = millis(); + if (now - lastLoop > transitionTime) { + if (loopCount <= 1020) { + lastLoop = now; + + redVal = calculateVal(stepR, redVal, loopCount); + grnVal = calculateVal(stepG, grnVal, loopCount); + bluVal = calculateVal(stepB, bluVal, loopCount); + whtVal = calculateVal(stepW, whtVal, loopCount); + + setColor(redVal, grnVal, bluVal, whtVal); // Write current values to LED pins + + Serial.print("Loop count: "); + Serial.println(loopCount); + loopCount++; + } + else { + inFade = false; + } + } + } +} + +// From https://www.arduino.cc/en/Tutorial/ColorCrossfader +/* BELOW THIS LINE IS THE MATH -- YOU SHOULDN'T NEED TO CHANGE THIS FOR THE BASICS +* +* The program works like this: +* Imagine a crossfade that moves the red LED from 0-10, +* the green from 0-5, and the blue from 10 to 7, in +* ten steps. +* We'd want to count the 10 steps and increase or +* decrease color values in evenly stepped increments. +* Imagine a + indicates raising a value by 1, and a - +* equals lowering it. Our 10 step fade would look like: +* +* 1 2 3 4 5 6 7 8 9 10 +* R + + + + + + + + + + +* G + + + + + +* B - - - +* +* The red rises from 0 to 10 in ten steps, the green from +* 0-5 in 5 steps, and the blue falls from 10 to 7 in three steps. +* +* In the real program, the color percentages are converted to +* 0-255 values, and there are 1020 steps (255*4). +* +* To figure out how big a step there should be between one up- or +* down-tick of one of the LED values, we call calculateStep(), +* which calculates the absolute gap between the start and end values, +* and then divides that gap by 1020 to determine the size of the step +* between adjustments in the value. +*/ +int calculateStep(int prevValue, int endValue) { + int step = endValue - prevValue; // What's the overall gap? + if (step) { // If its non-zero, + step = 1020/step; // divide by 1020 + } + + return step; +} + +/* The next function is calculateVal. When the loop value, i, +* reaches the step size appropriate for one of the +* colors, it increases or decreases the value of that color by 1. +* (R, G, and B are each calculated separately.) +*/ +int calculateVal(int step, int val, int i) { + if ((step) && i % step == 0) { // If step is non-zero and its time to change a value, + if (step > 0) { // increment the value if step is positive... + val += 1; + } + else if (step < 0) { // ...or decrement it if step is negative + val -= 1; + } + } + + // Defensive driving: make sure val stays in the range 0-255 + if (val > 255) { + val = 255; + } + else if (val < 0) { + val = 0; + } + + return val; +}