From dbd6f134c192c7e93b471836a3de84b6817734ea Mon Sep 17 00:00:00 2001 From: cschwinne Date: Fri, 28 Sep 2018 23:53:51 +0200 Subject: [PATCH] Added CL= and C2= API calls to set HEX or DEC RGB or WRGB color Started to add MQTT support Pre server and init split-up --- wled00/WS2812FX.cpp | 1 + .../src/dependencies/pubsubclient/LICENSE.txt | 20 + .../pubsubclient/PubSubClient.cpp | 601 ++++++++++++++++++ .../dependencies/pubsubclient/PubSubClient.h | 144 +++++ wled00/wled00.ino | 23 +- wled00/wled02_xml.ino | 4 +- wled00/wled03_set.ino | 15 +- wled00/wled05_init.ino | 5 + wled00/wled12_alexa.ino | 24 +- wled00/wled14_colors.ino | 20 + wled00/wled17_mqtt.ino | 96 +++ 11 files changed, 932 insertions(+), 21 deletions(-) create mode 100644 wled00/src/dependencies/pubsubclient/LICENSE.txt create mode 100644 wled00/src/dependencies/pubsubclient/PubSubClient.cpp create mode 100644 wled00/src/dependencies/pubsubclient/PubSubClient.h create mode 100644 wled00/wled17_mqtt.ino diff --git a/wled00/WS2812FX.cpp b/wled00/WS2812FX.cpp index 8473b14b..8f4af282 100644 --- a/wled00/WS2812FX.cpp +++ b/wled00/WS2812FX.cpp @@ -38,6 +38,7 @@ Modified for WLED */ + #include "WS2812FX.h" #include "FastLED.h" #include "palettes.h"; diff --git a/wled00/src/dependencies/pubsubclient/LICENSE.txt b/wled00/src/dependencies/pubsubclient/LICENSE.txt new file mode 100644 index 00000000..217df35c --- /dev/null +++ b/wled00/src/dependencies/pubsubclient/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2015 Nicholas O'Leary + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/wled00/src/dependencies/pubsubclient/PubSubClient.cpp b/wled00/src/dependencies/pubsubclient/PubSubClient.cpp new file mode 100644 index 00000000..6ea7e055 --- /dev/null +++ b/wled00/src/dependencies/pubsubclient/PubSubClient.cpp @@ -0,0 +1,601 @@ +/* + PubSubClient.cpp - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#include "PubSubClient.h" +#include "Arduino.h" + +PubSubClient::PubSubClient() { + this->_state = MQTT_DISCONNECTED; + this->_client = NULL; + this->stream = NULL; + setCallback(NULL); +} + +PubSubClient::PubSubClient(Client& client) { + this->_state = MQTT_DISCONNECTED; + setClient(client); + this->stream = NULL; +} + +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setClient(client); + this->stream = NULL; +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setClient(client); + setStream(stream); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); + this->stream = NULL; +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setCallback(callback); + setClient(client); + setStream(stream); +} + +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setClient(client); + this->stream = NULL; +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setClient(client); + setStream(stream); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setCallback(callback); + setClient(client); + this->stream = NULL; +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setCallback(callback); + setClient(client); + setStream(stream); +} + +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + this->stream = NULL; +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + setStream(stream); +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + this->stream = NULL; +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + setStream(stream); +} + +boolean PubSubClient::connect(const char *id) { + return connect(id,NULL,NULL,0,0,0,0); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { + return connect(id,user,pass,0,0,0,0); +} + +boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + if (!connected()) { + int result = 0; + + if (domain != NULL) { + result = _client->connect(this->domain, this->port); + } else { + result = _client->connect(this->ip, this->port); + } + if (result == 1) { + nextMsgId = 1; + // Leave room in the buffer for header and variable length field + uint16_t length = 5; + unsigned int j; + +#if MQTT_VERSION == MQTT_VERSION_3_1 + uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 9 +#elif MQTT_VERSION == MQTT_VERSION_3_1_1 + uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 7 +#endif + for (j = 0;j>1); + } + } + + buffer[length++] = v; + + buffer[length++] = ((MQTT_KEEPALIVE) >> 8); + buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF); + length = writeString(id,buffer,length); + if (willTopic) { + length = writeString(willTopic,buffer,length); + length = writeString(willMessage,buffer,length); + } + + if(user != NULL) { + length = writeString(user,buffer,length); + if(pass != NULL) { + length = writeString(pass,buffer,length); + } + } + + write(MQTTCONNECT,buffer,length-5); + + lastInActivity = lastOutActivity = millis(); + + while (!_client->available()) { + unsigned long t = millis(); + if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) { + _state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } + } + uint8_t llen; + uint16_t len = readPacket(&llen); + + if (len == 4) { + if (buffer[3] == 0) { + lastInActivity = millis(); + pingOutstanding = false; + _state = MQTT_CONNECTED; + return true; + } else { + _state = buffer[3]; + } + } + _client->stop(); + } else { + _state = MQTT_CONNECT_FAILED; + } + return false; + } + return true; +} + +// reads a byte into result +boolean PubSubClient::readByte(uint8_t * result) { + uint32_t previousMillis = millis(); + while(!_client->available()) { + uint32_t currentMillis = millis(); + if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){ + return false; + } + } + *result = _client->read(); + return true; +} + +// reads a byte into result[*index] and increments index +boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ + uint16_t current_index = *index; + uint8_t * write_address = &(result[current_index]); + if(readByte(write_address)){ + *index = current_index + 1; + return true; + } + return false; +} + +uint16_t PubSubClient::readPacket(uint8_t* lengthLength) { + uint16_t len = 0; + if(!readByte(buffer, &len)) return 0; + bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH; + uint32_t multiplier = 1; + uint16_t length = 0; + uint8_t digit = 0; + uint16_t skip = 0; + uint8_t start = 0; + + do { + if (len == 6) { + // Invalid remaining length encoding - kill the connection + _state = MQTT_DISCONNECTED; + _client->stop(); + return 0; + } + if(!readByte(&digit)) return 0; + buffer[len++] = digit; + length += (digit & 127) * multiplier; + multiplier *= 128; + } while ((digit & 128) != 0); + *lengthLength = len-1; + + if (isPublish) { + // Read in topic length to calculate bytes to skip over for Stream writing + if(!readByte(buffer, &len)) return 0; + if(!readByte(buffer, &len)) return 0; + skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2]; + start = 2; + if (buffer[0]&MQTTQOS1) { + // skip message id + skip += 2; + } + } + + for (uint16_t i = start;istream) { + if (isPublish && len-*lengthLength-2>skip) { + this->stream->write(digit); + } + } + if (len < MQTT_MAX_PACKET_SIZE) { + buffer[len] = digit; + } + len++; + } + + if (!this->stream && len > MQTT_MAX_PACKET_SIZE) { + len = 0; // This will cause the packet to be ignored. + } + + return len; +} + +boolean PubSubClient::loop() { + if (connected()) { + unsigned long t = millis(); + if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) { + if (pingOutstanding) { + this->_state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } else { + buffer[0] = MQTTPINGREQ; + buffer[1] = 0; + _client->write(buffer,2); + lastOutActivity = t; + lastInActivity = t; + pingOutstanding = true; + } + } + if (_client->available()) { + uint8_t llen; + uint16_t len = readPacket(&llen); + uint16_t msgId = 0; + uint8_t *payload; + if (len > 0) { + lastInActivity = t; + uint8_t type = buffer[0]&0xF0; + if (type == MQTTPUBLISH) { + if (callback) { + uint16_t tl = (buffer[llen+1]<<8)+buffer[llen+2]; /* topic length in bytes */ + memmove(buffer+llen+2,buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */ + buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */ + char *topic = (char*) buffer+llen+2; + + // make sure payload can be interpreted as 'C' string + buffer[(len < MQTT_MAX_PACKET_SIZE) ? len -1 : MQTT_MAX_PACKET_SIZE -1] = 0; + + // msgId only present for QOS>0 + if ((buffer[0]&0x06) == MQTTQOS1) { + msgId = (buffer[llen+3+tl]<<8)+buffer[llen+3+tl+1]; + payload = buffer+llen+3+tl+2; + callback(topic,payload,len-llen-3-tl-2); + + buffer[0] = MQTTPUBACK; + buffer[1] = 2; + buffer[2] = (msgId >> 8); + buffer[3] = (msgId & 0xFF); + _client->write(buffer,4); + lastOutActivity = t; + + } else { + payload = buffer+llen+3+tl; + callback(topic,payload,len-llen-3-tl); + } + } + } else if (type == MQTTPINGREQ) { + buffer[0] = MQTTPINGRESP; + buffer[1] = 0; + _client->write(buffer,2); + } else if (type == MQTTPINGRESP) { + pingOutstanding = false; + } + } else if (!connected()) { + // readPacket has closed the connection + return false; + } + } + return true; + } + return false; +} + +boolean PubSubClient::publish(const char* topic, const char* payload) { + return publish(topic,(const uint8_t*)payload,strlen(payload),false); +} + +boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { + return publish(topic,(const uint8_t*)payload,strlen(payload),retained); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { + return publish(topic, payload, plength, false); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { + if (connected()) { + if (MQTT_MAX_PACKET_SIZE < 5 + 2+strlen(topic) + plength) { + // Too long + return false; + } + // Leave room in the buffer for header and variable length field + uint16_t length = 5; + length = writeString(topic,buffer,length); + uint16_t i; + for (i=0;i 0) { + digit |= 0x80; + } + buffer[pos++] = digit; + llen++; + } while(len>0); + + pos = writeString(topic,buffer,pos); + + rc += _client->write(buffer,pos); + + for (i=0;iwrite((char)pgm_read_byte_near(payload + i)); + } + + lastOutActivity = millis(); + + return rc == tlen + 4 + plength; +} + +boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { + uint8_t lenBuf[4]; + uint8_t llen = 0; + uint8_t digit; + uint8_t pos = 0; + uint16_t rc; + uint16_t len = length; + do { + digit = len % 128; + len = len / 128; + if (len > 0) { + digit |= 0x80; + } + lenBuf[pos++] = digit; + llen++; + } while(len>0); + + buf[4-llen] = header; + for (int i=0;i 0) && result) { + bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; + rc = _client->write(writeBuf,bytesToWrite); + result = (rc == bytesToWrite); + bytesRemaining -= rc; + writeBuf += rc; + } + return result; +#else + rc = _client->write(buf+(4-llen),length+1+llen); + lastOutActivity = millis(); + return (rc == 1+llen+length); +#endif +} + +boolean PubSubClient::subscribe(const char* topic) { + return subscribe(topic, 0); +} + +boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { + if (qos > 1) { + return false; + } + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { + // Too long + return false; + } + if (connected()) { + // Leave room in the buffer for header and variable length field + uint16_t length = 5; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString((char*)topic, buffer,length); + buffer[length++] = qos; + return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-5); + } + return false; +} + +boolean PubSubClient::unsubscribe(const char* topic) { + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { + // Too long + return false; + } + if (connected()) { + uint16_t length = 5; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, buffer,length); + return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-5); + } + return false; +} + +void PubSubClient::disconnect() { + buffer[0] = MQTTDISCONNECT; + buffer[1] = 0; + _client->write(buffer,2); + _state = MQTT_DISCONNECTED; + _client->stop(); + lastInActivity = lastOutActivity = millis(); +} + +uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { + const char* idp = string; + uint16_t i = 0; + pos += 2; + while (*idp) { + buf[pos++] = *idp++; + i++; + } + buf[pos-i-2] = (i >> 8); + buf[pos-i-1] = (i & 0xFF); + return pos; +} + + +boolean PubSubClient::connected() { + boolean rc; + if (_client == NULL ) { + rc = false; + } else { + rc = (int)_client->connected(); + if (!rc) { + if (this->_state == MQTT_CONNECTED) { + this->_state = MQTT_CONNECTION_LOST; + _client->flush(); + _client->stop(); + } + } + } + return rc; +} + +PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { + IPAddress addr(ip[0],ip[1],ip[2],ip[3]); + return setServer(addr,port); +} + +PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { + this->ip = ip; + this->port = port; + this->domain = NULL; + return *this; +} + +PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { + this->domain = domain; + this->port = port; + return *this; +} + +PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { + this->callback = callback; + return *this; +} + +PubSubClient& PubSubClient::setClient(Client& client){ + this->_client = &client; + return *this; +} + +PubSubClient& PubSubClient::setStream(Stream& stream){ + this->stream = &stream; + return *this; +} + +int PubSubClient::state() { + return this->_state; +} diff --git a/wled00/src/dependencies/pubsubclient/PubSubClient.h b/wled00/src/dependencies/pubsubclient/PubSubClient.h new file mode 100644 index 00000000..be4bd674 --- /dev/null +++ b/wled00/src/dependencies/pubsubclient/PubSubClient.h @@ -0,0 +1,144 @@ +/* + PubSubClient.h - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#ifndef PubSubClient_h +#define PubSubClient_h + +#include +#include "IPAddress.h" +#include "Client.h" +#include "Stream.h" + +#define MQTT_VERSION_3_1 3 +#define MQTT_VERSION_3_1_1 4 + +// MQTT_VERSION : Pick the version +//#define MQTT_VERSION MQTT_VERSION_3_1 +#ifndef MQTT_VERSION +#define MQTT_VERSION MQTT_VERSION_3_1_1 +#endif + +// MQTT_MAX_PACKET_SIZE : Maximum packet size +#ifndef MQTT_MAX_PACKET_SIZE +#define MQTT_MAX_PACKET_SIZE 128 +#endif + +// MQTT_KEEPALIVE : keepAlive interval in Seconds +#ifndef MQTT_KEEPALIVE +#define MQTT_KEEPALIVE 15 +#endif + +// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds +#ifndef MQTT_SOCKET_TIMEOUT +#define MQTT_SOCKET_TIMEOUT 15 +#endif + +// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client +// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to +// pass the entire MQTT packet in each write call. +//#define MQTT_MAX_TRANSFER_SIZE 80 + +// Possible values for client.state() +#define MQTT_CONNECTION_TIMEOUT -4 +#define MQTT_CONNECTION_LOST -3 +#define MQTT_CONNECT_FAILED -2 +#define MQTT_DISCONNECTED -1 +#define MQTT_CONNECTED 0 +#define MQTT_CONNECT_BAD_PROTOCOL 1 +#define MQTT_CONNECT_BAD_CLIENT_ID 2 +#define MQTT_CONNECT_UNAVAILABLE 3 +#define MQTT_CONNECT_BAD_CREDENTIALS 4 +#define MQTT_CONNECT_UNAUTHORIZED 5 + +#define MQTTCONNECT 1 << 4 // Client request to connect to Server +#define MQTTCONNACK 2 << 4 // Connect Acknowledgment +#define MQTTPUBLISH 3 << 4 // Publish message +#define MQTTPUBACK 4 << 4 // Publish Acknowledgment +#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1) +#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2) +#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3) +#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request +#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment +#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request +#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment +#define MQTTPINGREQ 12 << 4 // PING Request +#define MQTTPINGRESP 13 << 4 // PING Response +#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting +#define MQTTReserved 15 << 4 // Reserved + +#define MQTTQOS0 (0 << 1) +#define MQTTQOS1 (1 << 1) +#define MQTTQOS2 (2 << 1) + +#ifdef ESP8266 +#include +#define MQTT_CALLBACK_SIGNATURE std::function callback +#else +#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int) +#endif + +class PubSubClient { +private: + Client* _client; + uint8_t buffer[MQTT_MAX_PACKET_SIZE]; + uint16_t nextMsgId; + unsigned long lastOutActivity; + unsigned long lastInActivity; + bool pingOutstanding; + MQTT_CALLBACK_SIGNATURE; + uint16_t readPacket(uint8_t*); + boolean readByte(uint8_t * result); + boolean readByte(uint8_t * result, uint16_t * index); + boolean write(uint8_t header, uint8_t* buf, uint16_t length); + uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); + IPAddress ip; + const char* domain; + uint16_t port; + Stream* stream; + int _state; +public: + PubSubClient(); + PubSubClient(Client& client); + PubSubClient(IPAddress, uint16_t, Client& client); + PubSubClient(IPAddress, uint16_t, Client& client, Stream&); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, Client& client); + PubSubClient(uint8_t *, uint16_t, Client& client, Stream&); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + PubSubClient(const char*, uint16_t, Client& client); + PubSubClient(const char*, uint16_t, Client& client, Stream&); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client); + PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&); + + PubSubClient& setServer(IPAddress ip, uint16_t port); + PubSubClient& setServer(uint8_t * ip, uint16_t port); + PubSubClient& setServer(const char * domain, uint16_t port); + PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); + PubSubClient& setClient(Client& client); + PubSubClient& setStream(Stream& stream); + + boolean connect(const char* id); + boolean connect(const char* id, const char* user, const char* pass); + boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage); + void disconnect(); + boolean publish(const char* topic, const char* payload); + boolean publish(const char* topic, const char* payload, boolean retained); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength); + boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); + boolean subscribe(const char* topic); + boolean subscribe(const char* topic, uint8_t qos); + boolean unsubscribe(const char* topic); + boolean loop(); + boolean connected(); + int state(); +}; + + +#endif diff --git a/wled00/wled00.ino b/wled00/wled00.ino index 508a980f..e04bffea 100644 --- a/wled00/wled00.ino +++ b/wled00/wled00.ino @@ -35,16 +35,17 @@ #include "src/dependencies/time/Time.h" #include "src/dependencies/time/TimeLib.h" #include "src/dependencies/timezone/Timezone.h" +#include "src/dependencies/blynk/BlynkSimpleEsp.h" +#include "src/dependencies/e131/E131.h" +#include "src/dependencies/pubsubclient/PubSubClient.h" #include "htmls00.h" #include "htmls01.h" #include "htmls02.h" #include "WS2812FX.h" -#include "src/dependencies/blynk/BlynkSimpleEsp.h" -#include "src/dependencies/e131/E131.h" //version code in format yymmddb (b = daily build) -#define VERSION 1809222 +#define VERSION 1809281 char versionString[] = "0.8.0-a"; @@ -160,6 +161,10 @@ bool e131Enabled = true; //settings for E1.31 (sACN) protoc uint16_t e131Universe = 1; bool e131Multicast = false; +char mqttTopic0[33] = ""; //main MQTT topic (individual per device, default is wled/mac) +char mqttTopic1[33] = "wled/all"; //second MQTT topic (for example to group devices) +char mqttServer[33] = "37.187.106.16"; //both domains and IPs should work (no SSL) 37.187.106.16 + bool huePollingEnabled = false; //poll hue bridge for light state uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response char hueApiKey[65] = "api"; //key token will be obtained from bridge @@ -324,13 +329,17 @@ bool realtimeActive = false; IPAddress realtimeIP = (0,0,0,0); unsigned long realtimeTimeout = 0; +//mqtt +bool mqttInit = false; +long lastMQTTReconnectAttempt = 0; + //auxiliary debug pin byte auxTime = 0; unsigned long auxStartTime = 0; bool auxActive = false, auxActiveBefore = false; //alexa udp -WiFiUDP UDP; +WiFiUDP alexaUDP; IPAddress ipMulti(239, 255, 255, 250); unsigned int portMulti = 1900; String escapedMac; @@ -360,6 +369,9 @@ WebServer server(80); ESP8266WebServer server(80); #endif HTTPClient hueClient; +WiFiClient mqttTCPClient; +PubSubClient mqtt(mqttTCPClient); + ESP8266HTTPUpdateServer httpUpdater; //udp interface objects @@ -465,6 +477,9 @@ void loop() { if (aOtaEnabled) ArduinoOTA.handle(); handleAlexa(); handleOverlays(); + + yield(); + handleMQTT(); if (!realtimeActive) //block stuff if WARLS/Adalight is enabled { diff --git a/wled00/wled02_xml.ino b/wled00/wled02_xml.ino index 9dfc2451..8aaca563 100644 --- a/wled00/wled02_xml.ino +++ b/wled00/wled02_xml.ino @@ -2,7 +2,7 @@ * Sending XML status files to client */ -void XML_response() +void XML_response(bool isHTTP) { olen = 0; oappend(""); @@ -52,7 +52,7 @@ void XML_response() oappend(""); oappend(serverDescription); oappend(""); - server.send(200, "text/xml", obuf); + if (isHTTP) server.send(200, "text/xml", obuf); } void sappend(char stype, char* key, int val) //append a setting to string buffer diff --git a/wled00/wled03_set.ino b/wled00/wled03_set.ino index 714c8f5a..487c6fa2 100644 --- a/wled00/wled03_set.ino +++ b/wled00/wled03_set.ino @@ -101,6 +101,7 @@ void handleSettingsSet(byte subPage) fadeTransition = server.hasArg("TF"); t = server.arg("TD").toInt(); if (t > 0) transitionDelay = t; + transitionDelayDefault = t; strip.paletteFade = server.hasArg("PF"); enableSecTransition = server.hasArg("T2"); @@ -318,7 +319,7 @@ bool handleSet(String req) } pos = req.indexOf("IN"); - if (pos < 1) XML_response(); + if (pos < 1) XML_response(true); return true; //if you save a macro in one request, other commands in that request are ignored due to unwanted behavior otherwise } @@ -383,6 +384,16 @@ bool handleSet(String req) whiteSec = req.substring(pos + 3).toInt(); } + //set color from HEX or 32bit DEC + pos = req.indexOf("CL="); + if (pos > 0) { + colorFromDecOrHexString(col, &white, (char*)req.substring(pos + 3).c_str()); + } + pos = req.indexOf("C2="); + if (pos > 0) { + colorFromDecOrHexString(colSec, &whiteSec, (char*)req.substring(pos + 3).c_str()); + } + //set 2nd to white pos = req.indexOf("SW"); if (pos > 0) { @@ -718,7 +729,7 @@ bool handleSet(String req) //internal call, does not send XML response pos = req.indexOf("IN"); - if (pos < 1) XML_response(); + if (pos < 1) XML_response(true); //do not send UDP notifications this time pos = req.indexOf("NN"); if (pos > 0) diff --git a/wled00/wled05_init.ino b/wled00/wled05_init.ino index 00472f9a..ac20e2ee 100644 --- a/wled00/wled05_init.ino +++ b/wled00/wled05_init.ino @@ -56,6 +56,10 @@ void wledInit() dnsServer.start(53, "*", WiFi.softAPIP()); dnsActive = true; } + + prepareIds(); //UUID from MAC (for Alexa and MQTT) + if (mqttTopic0[0] == 0) strcpy(mqttTopic0, strcat("wled/", escapedMac.c_str())); + if (!initLedsLast) strip.service(); //SERVER INIT //settings page @@ -264,6 +268,7 @@ void wledInit() { initBlynk(blynkApiKey); initE131(); + mqttInit = initMQTT(); } if (initLedsLast) initStrip(); diff --git a/wled00/wled12_alexa.ino b/wled00/wled12_alexa.ino index f501a694..05f3ebbf 100644 --- a/wled00/wled12_alexa.ino +++ b/wled00/wled12_alexa.ino @@ -10,8 +10,6 @@ void alexaInit() { if (alexaEnabled && WiFi.status() == WL_CONNECTED) { - prepareIds(); - udpConnected = connectUDP(); if (udpConnected) alexaInitPages(); @@ -24,10 +22,10 @@ void handleAlexa() { if(udpConnected){ // if there’s data available, read a packet - int packetSize = UDP.parsePacket(); + int packetSize = alexaUDP.parsePacket(); if(packetSize>0) { - IPAddress remote = UDP.remoteIP(); - int len = UDP.read(obuf, 254); + IPAddress remote = alexaUDP.remoteIP(); + int len = alexaUDP.read(obuf, 254); if (len > 0) { obuf[len] = 0; } @@ -98,9 +96,9 @@ void prepareIds() { void respondToSearch() { DEBUG_PRINTLN(""); DEBUG_PRINT("Send resp to "); - DEBUG_PRINTLN(UDP.remoteIP()); + DEBUG_PRINTLN(alexaUDP.remoteIP()); DEBUG_PRINT("Port : "); - DEBUG_PRINTLN(UDP.remotePort()); + DEBUG_PRINTLN(alexaUDP.remotePort()); IPAddress localIP = WiFi.localIP(); char s[16]; @@ -124,13 +122,13 @@ void respondToSearch() { oappend("::upnp:rootdevice\r\n" // _uuid::_deviceType "\r\n"); - UDP.beginPacket(UDP.remoteIP(), UDP.remotePort()); + alexaUDP.beginPacket(alexaUDP.remoteIP(), alexaUDP.remotePort()); #ifdef ARDUINO_ARCH_ESP32 - UDP.write((byte*)obuf, olen); + alexaUDP.write((byte*)obuf, olen); #else - UDP.write(obuf); + alexaUDP.write(obuf); #endif - UDP.endPacket(); + alexaUDP.endPacket(); DEBUG_PRINTLN("Response sent!"); } @@ -276,9 +274,9 @@ bool connectUDP(){ DEBUG_PRINTLN("Con UDP"); #ifdef ARDUINO_ARCH_ESP32 - if(UDP.beginMulticast(ipMulti, portMulti)) + if(alexaUDP.beginMulticast(ipMulti, portMulti)) #else - if(UDP.beginMulticast(WiFi.localIP(), ipMulti, portMulti)) + if(alexaUDP.beginMulticast(WiFi.localIP(), ipMulti, portMulti)) #endif { DEBUG_PRINTLN("Con success"); diff --git a/wled00/wled14_colors.ino b/wled00/wled14_colors.ino index 6f882299..ed008a84 100644 --- a/wled00/wled14_colors.ino +++ b/wled00/wled14_colors.ino @@ -106,6 +106,26 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www rgb[2] = 255.0*b; } +void colorFromDecOrHexString(byte* rgb, byte* wht, char* in) +{ + if (in[0] == 0) return; + char first = in[0]; + uint32_t c = 0; + + if (first == '#' || first == 'h' || first == 'H') //is HEX encoded + { + c = strtoul(in +1, NULL, 16); + } else + { + c = strtoul(in, NULL, 10); + } + + *wht = (c >> 24) & 0xFF; + rgb[0] = (c >> 16) & 0xFF; + rgb[1] = (c >> 8) & 0xFF; + rgb[2] = c & 0xFF; +} + void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) { float X = rgb[0] * 0.664511f + rgb[1] * 0.154324f + rgb[2] * 0.162028f; diff --git a/wled00/wled17_mqtt.ino b/wled00/wled17_mqtt.ino new file mode 100644 index 00000000..85918c65 --- /dev/null +++ b/wled00/wled17_mqtt.ino @@ -0,0 +1,96 @@ +/* + * MQTT communication protocol for home automation + */ + +void callbackMQTT(char* topic, byte* payload, unsigned int length) { + + if (strcmp(topic, mqttTopic0) == 0 || + strcmp(topic, mqttTopic1) == 0) + { + if (strcmp((char*)payload, "ON") == 0) {bri = briLast;} + else if (strcmp((char*)payload, "T" ) == 0) {handleSet("win&T=2");} + else { + uint8_t in = strtoul((char*)payload, NULL, 10); + if (in == 0 && bri > 0) briLast = bri; + bri = in; + } + colorUpdated(1); + return; + } + + if (strcmp(topic, strcat(mqttTopic0, "/col")) == 0 || + strcmp(topic, strcat(mqttTopic1, "/col")) == 0) + { + colorFromDecOrHexString(col, &white, (char*)payload); + colorUpdated(1); + return; + } + + if (strcmp(topic, strcat(mqttTopic0, "/api")) == 0 || + strcmp(topic, strcat(mqttTopic1, "/api")) == 0) + { + handleSet(String((char*)payload)); + return; + } +} + +void publishStatus() +{ + if (!mqtt.connected()) return; + + char s[4]; + sprintf(s,"%ld", bri); + mqtt.publish(strcat(mqttTopic0, "/g") , s); + XML_response(false); + mqtt.publish(strcat(mqttTopic0, "/vs"), obuf); +} + +bool reconnectMQTT() +{ + if (mqtt.connect(escapedMac.c_str())) + { + //re-subscribe to required topics + + if (mqttTopic0[0] != 0) + { + mqtt.subscribe(mqttTopic0); + mqtt.subscribe(strcat(mqttTopic0, "/col")); + mqtt.subscribe(strcat(mqttTopic0, "/api")); + } + + if (mqttTopic1[0] != 0) + { + mqtt.subscribe(mqttTopic1); + mqtt.subscribe(strcat(mqttTopic1, "/col")); + mqtt.subscribe(strcat(mqttTopic1, "/api")); + } + } + return mqtt.connected(); +} + +bool initMQTT() +{ + if (WiFi.status() != WL_CONNECTED) return false; + if (mqttServer[0] == 0) return false; + + IPAddress mqttIP; + if (mqttIP.fromString(mqttServer)) //see if server is IP or domain + { + mqtt.setServer(mqttIP,1883); + } else { + mqtt.setServer(mqttServer,1883); + } + mqtt.setCallback(callbackMQTT); + return true; +} + +void handleMQTT() +{ + if (WiFi.status() != WL_CONNECTED || !mqttInit) return; + if (!mqtt.connected() && millis() - lastMQTTReconnectAttempt > 5000) + { + lastMQTTReconnectAttempt = millis(); + if (!reconnectMQTT()) return; + } + mqtt.loop(); +}