Merge branch 'feature/ha_integration'

This commit is contained in:
Tobias Blum 2018-03-18 00:12:28 +01:00
commit 075fd67f5c
5 changed files with 569 additions and 160 deletions

View file

@ -27,6 +27,9 @@
// MQTT
#ifdef ENABLE_MQTT
#include <PubSubClient.h>
#ifdef ENABLE_HOMEASSISTANT
#include <ArduinoJson.h>
#endif
WiFiClient espClient;
PubSubClient mqtt_client(espClient);

View file

@ -1,13 +1,14 @@
// Neopixel
#define PIN 5 // PIN (5 / D1) where neopixel / WS2811 strip is attached
#define PIN 5 // PIN (14 / D5) where neopixel / WS2811 strip is attached
#define NUMLEDS 24 // Number of leds in the strip
//#define BUILTIN_LED 2 // ESP-12F has the built in LED on GPIO2, see https://github.com/esp8266/Arduino/issues/2192
#define BUILTIN_LED 2 // ESP-12F has the built in LED on GPIO2, see https://github.com/esp8266/Arduino/issues/2192
#define BUTTON 4 // Input pin (4 / D2) for switching the LED strip on / off, connect this PIN to ground to trigger button.
const char HOSTNAME[] = "ESP8266_01"; // Friedly hostname
const char HOSTNAME[] = "McLighting01"; // Friedly hostname
#define ENABLE_OTA // If defined, enable Arduino OTA code.
#define ENABLE_MQTT // If defined, enable MQTT client code, see: https://github.com/toblum/McLighting/wiki/MQTT-API
#define ENABLE_HOMEASSISTANT // If defined, enable Homeassistant integration, ENABLE_MQTT must be active
// #define ENABLE_BUTTON // If defined, enable button handling code, see: https://github.com/toblum/McLighting/wiki/Button-control
// parameters for automatically cycling favorite patterns
@ -19,14 +20,27 @@ uint32_t autoParams[][4] = { // color, speed, mode, duration (seconds)
};
#ifdef ENABLE_MQTT
#define MQTT_MAX_PACKET_SIZE 256
#define MQTT_MAX_PACKET_SIZE 512
#define MQTT_MAX_RECONNECT_TRIES 4
int mqtt_reconnect_retries = 0;
char mqtt_intopic[strlen(HOSTNAME) + 4]; // Topic in will be: <HOSTNAME>/in
char mqtt_outtopic[strlen(HOSTNAME) + 5]; // Topic out will be: <HOSTNAME>/out
char mqtt_intopic[strlen(HOSTNAME) + 4 + 5]; // Topic in will be: <HOSTNAME>/in
char mqtt_outtopic[strlen(HOSTNAME) + 5 + 5]; // Topic out will be: <HOSTNAME>/out
const char mqtt_clientid[] = "ESP8266Client"; // MQTT ClientID
#ifdef ENABLE_HOMEASSISTANT
String mqtt_ha = "home/" + String(HOSTNAME) + "_ha/";
String mqtt_ha_state_in = mqtt_ha + "state/in";
String mqtt_ha_state_out = mqtt_ha + "state/out";
String mqtt_ha_speed = mqtt_ha + "speed";
const char* on_cmd = "ON";
const char* off_cmd = "OFF";
bool stateOn = false;
bool animation_on = false;
String effectString = "Static";
#endif
const char mqtt_clientid[] = "NeoPixelsStrip"; // MQTT ClientID
char mqtt_host[64] = "";
char mqtt_port[6] = "";

View file

@ -191,30 +191,57 @@ void handleSetNamedMode(String str_mode) {
if (str_mode.startsWith("=off")) {
mode = OFF;
#ifdef ENABLE_HOMEASSISTANT
stateOn = false;
#endif
}
if (str_mode.startsWith("=all")) {
mode = ALL;
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
#endif
}
if (str_mode.startsWith("=wipe")) {
mode = WIPE;
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
#endif
}
if (str_mode.startsWith("=rainbow")) {
mode = RAINBOW;
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
#endif
}
if (str_mode.startsWith("=rainbowCycle")) {
mode = RAINBOWCYCLE;
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
#endif
}
if (str_mode.startsWith("=theaterchase")) {
mode = THEATERCHASE;
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
#endif
}
if (str_mode.startsWith("=twinkleRandom")) {
mode = TWINKLERANDOM;
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
#endif
}
if (str_mode.startsWith("=theaterchaseRainbow")) {
mode = THEATERCHASERAINBOW;
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
#endif
}
if (str_mode.startsWith("=tv")) {
mode = TV;
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
#endif
}
}
@ -259,6 +286,32 @@ void getModesJSON() {
server.send ( 200, "application/json", listModesJSON() );
}
#ifdef ENABLE_HOMEASSISTANT
/********************************** START SEND STATE*****************************************/
void sendState() {
StaticJsonBuffer<JSON_OBJECT_SIZE(10)> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["state"] = (stateOn) ? on_cmd : off_cmd;
JsonObject& color = root.createNestedObject("color");
color["r"] = main_color.red;
color["g"] = main_color.green;
color["b"] = main_color.blue;
root["brightness"] = brightness;
char modeName[30];
strncpy_P(modeName, (PGM_P)strip.getModeName(strip.getMode()), sizeof(modeName)); // copy from progmem
root["effect"] = modeName;
char buffer[root.measureLength() + 1];
root.printTo(buffer, sizeof(buffer));
mqtt_client.publish(mqtt_ha_state_out.c_str(), buffer, true);
}
#endif
// ***************************************************************************
// HTTP request handlers
@ -355,6 +408,10 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght
handleSetMainColor(payload);
DBG_OUTPUT_PORT.printf("Set main color to: [%u] [%u] [%u]\n", main_color.red, main_color.green, main_color.blue);
webSocket.sendTXT(num, "OK");
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
}
// ? ==> Set speed
@ -373,12 +430,20 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght
DBG_OUTPUT_PORT.printf("WS: Set brightness to: [%u]\n", brightness);
strip.setBrightness(brightness);
webSocket.sendTXT(num, "OK");
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
}
// * ==> Set main color and light all LEDs (Shortcut)
if (payload[0] == '*') {
handleSetAllMode(payload);
webSocket.sendTXT(num, "OK");
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
}
// ! ==> Set single LED in given color
@ -408,6 +473,9 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght
DBG_OUTPUT_PORT.printf("Activated mode [%u]!\n", mode);
webSocket.sendTXT(num, "OK");
#ifdef ENABLE_HOMEASSISTANT
sendState();
#endif
}
// $ ==> Get status Info.
@ -432,6 +500,10 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght
if (payload[0] == '/') {
handleSetWS2812FXMode(payload);
webSocket.sendTXT(num, "OK");
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
}
// start auto cycling
@ -452,6 +524,9 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght
void checkForRequests() {
webSocket.loop();
server.handleClient();
#ifdef ENABLE_MQTT
mqtt_client.loop();
#endif
}
@ -459,17 +534,167 @@ void checkForRequests() {
// MQTT callback / connection handler
// ***************************************************************************
#ifdef ENABLE_MQTT
#ifdef ENABLE_HOMEASSISTANT
void temp2rgb(unsigned int kelvin) {
int tmp_internal = kelvin / 100.0;
// red
if (tmp_internal <= 66) {
main_color.red = 255;
} else {
float tmp_red = 329.698727446 * pow(tmp_internal - 60, -0.1332047592);
if (tmp_red < 0) {
main_color.red = 0;
} else if (tmp_red > 255) {
main_color.red = 255;
} else {
main_color.red = tmp_red;
}
}
// green
if (tmp_internal <= 66) {
float tmp_green = 99.4708025861 * log(tmp_internal) - 161.1195681661;
if (tmp_green < 0) {
main_color.green = 0;
} else if (tmp_green > 255) {
main_color.green = 255;
} else {
main_color.green = tmp_green;
}
} else {
float tmp_green = 288.1221695283 * pow(tmp_internal - 60, -0.0755148492);
if (tmp_green < 0) {
main_color.green = 0;
} else if (tmp_green > 255) {
main_color.green = 255;
} else {
main_color.green = tmp_green;
}
}
// blue
if (tmp_internal >= 66) {
main_color.blue = 255;
} else if (tmp_internal <= 19) {
main_color.blue = 0;
} else {
float tmp_blue = 138.5177312231 * log(tmp_internal - 10) - 305.0447927307;
if (tmp_blue < 0) {
main_color.blue = 0;
} else if (tmp_blue > 255) {
main_color.blue = 255;
} else {
main_color.blue = tmp_blue;
}
}
//handleSetWS2812FXMode((uint8_t *) "/0");
strip.setColor(main_color.red, main_color.green, main_color.blue);
strip.start();
}
bool processJson(char* message) {
StaticJsonBuffer<JSON_OBJECT_SIZE(10)> 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 and !(animation_on)) {
stateOn = true;
mode = ALL;
strip.setColor(main_color.red, main_color.green, main_color.blue);
strip.start();
}
else if (strcmp(root["state"], off_cmd) == 0) {
stateOn = false;
mode = OFF;
animation_on = false;
strip.start();
}
}
if (root.containsKey("color")) {
main_color.red = root["color"]["r"];
main_color.green = root["color"]["g"];
main_color.blue = root["color"]["b"];
//handleSetWS2812FXMode((uint8_t *) "/0");
strip.setColor(main_color.red, main_color.green, main_color.blue);
strip.start();
}
if (root.containsKey("color_temp")) {
//temp comes in as mireds, need to convert to kelvin then to RGB
int color_temp = root["color_temp"];
unsigned int kelvin = 1000000 / color_temp;
temp2rgb(kelvin);
}
if (root.containsKey("brightness")) {
const char * brightness_json = root["brightness"];
uint8_t b = (uint8_t) strtol((const char *) &brightness_json[0], NULL, 10);
brightness = constrain(b, 0, 255);
strip.setBrightness(brightness);
}
if (root.containsKey("effect")) {
animation_on = true;
effectString = String((const char *)root["effect"]);
for (uint8_t i = 0; i < strip.getModeCount(); i++) {
if(String(strip.getModeName(i)) == effectString) {
mode = HOLD;
strip.setColor(main_color.red, main_color.green, main_color.blue);
strip.setMode(i);
strip.start();
break;
}
}
}
return true;
}
#endif
void mqtt_callback(char* topic, byte* payload_in, unsigned int length) {
uint8_t * payload = (uint8_t *)malloc(length + 1);
memcpy(payload, payload_in, length);
payload[length] = NULL;
DBG_OUTPUT_PORT.printf("MQTT: Message arrived [%s]\n", payload);
#ifdef ENABLE_HOMEASSISTANT
if (strcmp(topic, mqtt_ha_state_in.c_str()) == 0) {
if (!processJson((char*)payload)) {
return;
}
sendState();
} else if (strcmp(topic, mqtt_ha_speed.c_str()) == 0) {
uint8_t d = (uint8_t) strtol((const char *) &payload[0], NULL, 10);
ws2812fx_speed = constrain(d, 0, 255);
strip.setSpeed(convertSpeed(ws2812fx_speed));
} else if (strcmp(topic, (char *)mqtt_intopic) == 0) {
#endif
// # ==> Set main color
if (payload[0] == '#') {
handleSetMainColor(payload);
DBG_OUTPUT_PORT.printf("MQTT: Set main color to [%u] [%u] [%u]\n", main_color.red, main_color.green, main_color.blue);
mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str());
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
}
// ? ==> Set speed
@ -488,6 +713,10 @@ void mqtt_callback(char* topic, byte* payload_in, unsigned int length) {
strip.setBrightness(brightness);
DBG_OUTPUT_PORT.printf("MQTT: Set brightness to [%u]\n", brightness);
mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str());
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
}
// * ==> Set main color and light all LEDs (Shortcut)
@ -495,6 +724,10 @@ void mqtt_callback(char* topic, byte* payload_in, unsigned int length) {
handleSetAllMode(payload);
DBG_OUTPUT_PORT.printf("MQTT: Set main color and light all LEDs [%s]\n", payload);
mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str());
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
}
// ! ==> Set single LED in given color
@ -523,11 +756,15 @@ void mqtt_callback(char* topic, byte* payload_in, unsigned int length) {
handleSetNamedMode(str_mode);
DBG_OUTPUT_PORT.printf("MQTT: Activate named mode [%s]\n", payload);
mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str());
#ifdef ENABLE_HOMEASSISTANT
sendState();
#endif
}
// $ ==> Get status Info.
if (payload[0] == '$') {
DBG_OUTPUT_PORT.printf("MQTT: Get status info.\n");
DBG_OUTPUT_PORT.println("MQTT: Out: " + String(listStatusJSON()));
mqtt_client.publish(mqtt_outtopic, listStatusJSON());
}
@ -550,8 +787,15 @@ void mqtt_callback(char* topic, byte* payload_in, unsigned int length) {
handleSetWS2812FXMode(payload);
DBG_OUTPUT_PORT.printf("MQTT: Set WS2812 mode [%s]\n", payload);
mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str());
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
}
#ifdef ENABLE_HOMEASSISTANT
}
#endif
free(payload);
}
@ -570,6 +814,11 @@ void mqtt_reconnect() {
mqtt_client.publish(mqtt_outtopic, message);
// ... and resubscribe
mqtt_client.subscribe(mqtt_intopic);
#ifdef ENABLE_HOMEASSISTANT
DBG_OUTPUT_PORT.printf("Homeassistant MQTT topic in: %s\n", mqtt_ha_state_in.c_str());
mqtt_client.subscribe(mqtt_ha_state_in.c_str());
mqtt_client.subscribe(mqtt_ha_speed.c_str());
#endif
DBG_OUTPUT_PORT.printf("MQTT topic in: %s\n", mqtt_intopic);
DBG_OUTPUT_PORT.printf("MQTT topic out: %s\n", mqtt_outtopic);
@ -597,15 +846,9 @@ void shortKeyPress() {
if (buttonState == false) {
setModeByStateString(BTN_MODE_SHORT);
buttonState = true;
#ifdef ENABLE_MQTT
mqtt_client.publish(mqtt_outtopic, String("OK =static white").c_str());
#endif
} else {
mode = OFF;
buttonState = false;
#ifdef ENABLE_MQTT
mqtt_client.publish(mqtt_outtopic, String("OK =off").c_str());
#endif
}
}
@ -613,20 +856,12 @@ void shortKeyPress() {
void mediumKeyPress() {
DBG_OUTPUT_PORT.printf("Medium button press\n");
setModeByStateString(BTN_MODE_MEDIUM);
buttonState = true;
#ifdef ENABLE_MQTT
mqtt_client.publish(mqtt_outtopic, String("OK =fire flicker").c_str());
#endif
}
// called when button is kept pressed for 2 seconds or more
void longKeyPress() {
DBG_OUTPUT_PORT.printf("Long button press\n");
setModeByStateString(BTN_MODE_LONG);
buttonState = true;
#ifdef ENABLE_MQTT
mqtt_client.publish(mqtt_outtopic, String("OK =fireworks random").c_str());
#endif
}
void button() {
@ -639,6 +874,10 @@ void button() {
// key goes from not pressed to pressed
KeyPressCount = 0;
}
else if ((prevKeyState == LOW) && (currKeyState == HIGH)) {
if (KeyPressCount < longKeyPressCountMax && KeyPressCount >= mediumKeyPressCountMin) {
mediumKeyPress();
}
else if ((prevKeyState == LOW) && (currKeyState == HIGH)) {
if (KeyPressCount < longKeyPressCountMax && KeyPressCount >= mediumKeyPressCountMin) {
mediumKeyPress();
@ -659,3 +898,59 @@ void button() {
}
}
#endif
void shortKeyPress() {
DBG_OUTPUT_PORT.printf("Short button press\n");
if (buttonState == false) {
setModeByStateString(BTN_MODE_SHORT);
buttonState = true;
#ifdef ENABLE_MQTT
mqtt_client.publish(mqtt_outtopic, String("OK =static white").c_str());
#endif
} else {
mode = OFF;
buttonState = false;
#ifdef ENABLE_MQTT
mqtt_client.publish(mqtt_outtopic, String("OK =off").c_str());
#ifdef ENABLE_HOMEASSISTANT
stateOn = false;
sendState();
#endif
#endif
}
// called when button is kept pressed for less than 2 seconds
void mediumKeyPress() {
DBG_OUTPUT_PORT.printf("Medium button press\n");
setModeByStateString(BTN_MODE_MEDIUM);
#ifdef ENABLE_MQTT
mqtt_client.publish(mqtt_outtopic, String("OK =fire flicker").c_str());
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
#endif
}
// called when button is kept pressed for 2 seconds or more
void longKeyPress() {
DBG_OUTPUT_PORT.printf("Long button press\n");
setModeByStateString(BTN_MODE_LONG);
#ifdef ENABLE_MQTT
mqtt_client.publish(mqtt_outtopic, String("OK =fireworks random").c_str());
#ifdef ENABLE_HOMEASSISTANT
stateOn = true;
sendState();
#endif
#endif
}
void button() {
if (millis() - keyPrevMillis >= keySampleIntervalMs) {
keyPrevMillis = millis();
byte currKeyState = digitalRead(BUTTON);
if ((prevKeyState == HIGH) && (currKeyState == LOW)) {
// key goes from not pressed to pressed
KeyPressCount = 0;

View file

@ -7,6 +7,12 @@
> Because of it's open architecture and APIs it's easy to build new clients for different platforms (iOS, Android, Windows Universal Apps, Siri/Cortana integration, ...).
___
Update 15.02.2018:
Replaced Home Assistant Support using MQTT Light to MQTT JSON Light.
Update 12.02.2018:
Added Home Assistant Support using MQTT Light. A better implementation would be using MQTT Light JSON.
Update 17.02.2018:
User @debsahu contributed code for integration with homeassistant. It's currently in a separate branch (https://github.com/toblum/McLighting/tree/feature/ha_integration). If you're using Homeassistant, please try it out and give feedback.
User @FabLab-Luenn created a version of McLighting (https://github.com/FabLab-Luenen/McLighting) for 6812 and other RGBW strips. Give it a try, if you own such strips.

View file

@ -0,0 +1,91 @@
light:
- platform: mqtt_json
name: "NeoPixel LEDs"
state_topic: "home/McLighting01_ha/state/out"
command_topic: "home/McLighting01_ha/state/in"
effect: true
effect_list:
######
- "Blink"
- "Breath"
- "Color Wipe"
- "Color Wipe Inverse"
- "Color Wipe Reverse"
- "Color Wipe Reverse Inverse"
- "Color Wipe Random"
- "Random Color"
- "Single Dynamic"
- "Multi Dynamic"
- "Rainbow"
- "Rainbow Cycle"
- "Scan"
- "Dual Scan"
- "Fade"
- "Theater Chase"
- "Theater Chase Rainbow"
- "Running Lights"
- "Twinkle"
- "Twinkle Random"
- "Twinkle Fade"
- "Twinkle Fade Random"
- "Sparkle"
- "Flash Sparkle"
- "Hyper Sparkle"
- "Strobe"
- "Strobe Rainbow"
- "Multi Strobe"
- "Blink Rainbow"
- "Chase White"
- "Chase Color"
- "Chase Random"
- "Chase Rainbow"
- "Chase Flash"
- "Chase Flash Random"
- "Chase Rainbow White"
- "Chase Blackout"
- "Chase Blackout Rainbow"
- "Color Sweep Random"
- "Running Color"
- "Running Red Blue"
- "Running Random"
- "Larson Scanner"
- "Comet"
- "Fireworks"
- "Fireworks Random"
- "Merry Christmas"
- "Fire Flicker"
- "Fire Flicker (soft)"
- "Fire Flicker (intense)"
- "Circus Combustus"
- "Halloween"
- "Bicolor Chase"
- "Tricolor Chase"
- "ICU"
brightness: true
rgb: true
optimistic: false
qos: 0
retain: true
input_number:
neopixel_animation_speed:
name: NeoPixel Animation Speed
initial: 200
min: 0
max: 255
step: 5
automation:
- id: 71938579813759813757
alias: NeoPixel Animation Speed
initial_state: true
hide_entity: false
trigger:
- entity_id: input_number.neopixel_animation_speed
platform: state
action:
- data_template:
payload_template: '{{ trigger.to_state.state | int }}'
retain: true
topic: home/McLighting01_ha/speed
service: mqtt.publish