| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857 |
- /*
- xplg_wemohue.ino - wemo and hue support for Sonoff-Tasmota
- Copyright (C) 2018 Heiko Krupp and Theo Arends
- This program 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.
- This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
- */
- //#define min(a,b) ((a)<(b)?(a):(b))
- //#define max(a,b) ((a)>(b)?(a):(b))
- #if defined(USE_WEBSERVER) && defined(USE_EMULATION)
- /*********************************************************************************************\
- * Belkin WeMo and Philips Hue bridge emulation
- \*********************************************************************************************/
- #define UDP_BUFFER_SIZE 200 // Max UDP buffer size needed for M-SEARCH message
- #define UDP_MSEARCH_SEND_DELAY 1500 // Delay in ms before M-Search response is send
- #include <Ticker.h>
- Ticker TickerMSearch;
- boolean udp_connected = false;
- char packet_buffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP packet
- IPAddress ipMulticast(239,255,255,250); // Simple Service Discovery Protocol (SSDP)
- uint32_t port_multicast = 1900; // Multicast address and port
- bool udp_response_mutex = false; // M-Search response mutex to control re-entry
- IPAddress udp_remote_ip; // M-Search remote IP address
- uint16_t udp_remote_port; // M-Search remote port
- /*********************************************************************************************\
- * WeMo UPNP support routines
- \*********************************************************************************************/
- const char WEMO_MSEARCH[] PROGMEM =
- "HTTP/1.1 200 OK\r\n"
- "CACHE-CONTROL: max-age=86400\r\n"
- "DATE: Fri, 15 Apr 2016 04:56:29 GMT\r\n"
- "EXT:\r\n"
- "LOCATION: http://{r1:80/setup.xml\r\n"
- "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n"
- "01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n"
- "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"
- "ST: {r3\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice
- "USN: uuid:{r2::{r3\r\n" // type1 = urn:Belkin:device:**, type2 = upnp:rootdevice
- "X-User-Agent: redsonic\r\n"
- "\r\n";
- String WemoSerialnumber(void)
- {
- char serial[16];
- snprintf_P(serial, sizeof(serial), PSTR("201612K%08X"), ESP.getChipId());
- return String(serial);
- }
- String WemoUuid(void)
- {
- char uuid[27];
- snprintf_P(uuid, sizeof(uuid), PSTR("Socket-1_0-%s"), WemoSerialnumber().c_str());
- return String(uuid);
- }
- void WemoRespondToMSearch(int echo_type)
- {
- char message[TOPSZ];
- TickerMSearch.detach();
- if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
- String response = FPSTR(WEMO_MSEARCH);
- response.replace("{r1", WiFi.localIP().toString());
- response.replace("{r2", WemoUuid());
- if (1 == echo_type) { // type1 echo 1g & dot 2g
- response.replace("{r3", F("urn:Belkin:device:**"));
- } else { // type2 echo 2g (echo, plus, show)
- response.replace("{r3", F("upnp:rootdevice"));
- }
- PortUdp.write(response.c_str());
- PortUdp.endPacket();
- snprintf_P(message, sizeof(message), PSTR(D_RESPONSE_SENT));
- } else {
- snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
- }
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_UPNP D_WEMO " " D_JSON_TYPE " %d, %s " D_TO " %s:%d"),
- echo_type, message, udp_remote_ip.toString().c_str(), udp_remote_port);
- AddLog(LOG_LEVEL_DEBUG);
- udp_response_mutex = false;
- }
- /*********************************************************************************************\
- * Hue Bridge UPNP support routines
- * Need to send 3 response packets with varying ST and USN
- *
- * Using Espressif Inc Mac Address of 5C:CF:7F:00:00:00
- * Philips Lighting is 00:17:88:00:00:00
- \*********************************************************************************************/
- const char HUE_RESPONSE[] PROGMEM =
- "HTTP/1.1 200 OK\r\n"
- "HOST: 239.255.255.250:1900\r\n"
- "CACHE-CONTROL: max-age=100\r\n"
- "EXT:\r\n"
- "LOCATION: http://{r1:80/description.xml\r\n"
- "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.17.0\r\n"
- "hue-bridgeid: {r2\r\n";
- const char HUE_ST1[] PROGMEM =
- "ST: upnp:rootdevice\r\n"
- "USN: uuid:{r3::upnp:rootdevice\r\n"
- "\r\n";
- const char HUE_ST2[] PROGMEM =
- "ST: uuid:{r3\r\n"
- "USN: uuid:{r3\r\n"
- "\r\n";
- const char HUE_ST3[] PROGMEM =
- "ST: urn:schemas-upnp-org:device:basic:1\r\n"
- "USN: uuid:{r3\r\n"
- "\r\n";
- String HueBridgeId(void)
- {
- String temp = WiFi.macAddress();
- temp.replace(":", "");
- String bridgeid = temp.substring(0, 6) + "FFFE" + temp.substring(6);
- return bridgeid; // 5CCF7FFFFE139F3D
- }
- String HueSerialnumber(void)
- {
- String serial = WiFi.macAddress();
- serial.replace(":", "");
- serial.toLowerCase();
- return serial; // 5ccf7f139f3d
- }
- String HueUuid(void)
- {
- String uuid = F("f6543a06-da50-11ba-8d8f-");
- uuid += HueSerialnumber();
- return uuid; // f6543a06-da50-11ba-8d8f-5ccf7f139f3d
- }
- void HueRespondToMSearch(void)
- {
- char message[TOPSZ];
- TickerMSearch.detach();
- if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
- String response1 = FPSTR(HUE_RESPONSE);
- response1.replace("{r1", WiFi.localIP().toString());
- response1.replace("{r2", HueBridgeId());
- String response = response1;
- response += FPSTR(HUE_ST1);
- response.replace("{r3", HueUuid());
- PortUdp.write(response.c_str());
- PortUdp.endPacket();
- response = response1;
- response += FPSTR(HUE_ST2);
- response.replace("{r3", HueUuid());
- PortUdp.write(response.c_str());
- PortUdp.endPacket();
- response = response1;
- response += FPSTR(HUE_ST3);
- response.replace("{r3", HueUuid());
- PortUdp.write(response.c_str());
- PortUdp.endPacket();
- snprintf_P(message, sizeof(message), PSTR(D_3_RESPONSE_PACKETS_SENT));
- } else {
- snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
- }
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"),
- message, udp_remote_ip.toString().c_str(), udp_remote_port);
- AddLog(LOG_LEVEL_DEBUG);
- udp_response_mutex = false;
- }
- /*********************************************************************************************\
- * Belkin WeMo and Philips Hue bridge UDP multicast support
- \*********************************************************************************************/
- boolean UdpDisconnect(void)
- {
- if (udp_connected) {
- WiFiUDP::stopAll();
- AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED));
- udp_connected = false;
- }
- return udp_connected;
- }
- boolean UdpConnect(void)
- {
- if (!udp_connected) {
- if (PortUdp.beginMulticast(WiFi.localIP(), ipMulticast, port_multicast)) {
- AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED));
- udp_response_mutex = false;
- udp_connected = true;
- } else {
- AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED));
- udp_connected = false;
- }
- }
- return udp_connected;
- }
- void PollUdp(void)
- {
- if (udp_connected && !udp_response_mutex) {
- if (PortUdp.parsePacket()) {
- int len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1);
- if (len > 0) {
- packet_buffer[len] = 0;
- }
- String request = packet_buffer;
- // AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet received"));
- // AddLog_P(LOG_LEVEL_DEBUG_MORE, packet_buffer);
- if (request.indexOf("M-SEARCH") >= 0) {
- request.toLowerCase();
- request.replace(" ", "");
- // AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet received"));
- // AddLog_P(LOG_LEVEL_DEBUG_MORE, request.c_str());
- udp_remote_ip = PortUdp.remoteIP();
- udp_remote_port = PortUdp.remotePort();
- if (EMUL_WEMO == Settings.flag2.emulation) {
- if (request.indexOf(F("urn:belkin:device:**")) > 0) { // type1 echo dot 2g, echo 1g's
- udp_response_mutex = true;
- TickerMSearch.attach_ms(UDP_MSEARCH_SEND_DELAY, WemoRespondToMSearch, 1);
- }
- else if ((request.indexOf(F("upnp:rootdevice")) > 0) || // type2 Echo 2g (echo & echo plus)
- (request.indexOf(F("ssdpsearch:all")) > 0) ||
- (request.indexOf(F("ssdp:all")) > 0)) {
- udp_response_mutex = true;
- TickerMSearch.attach_ms(UDP_MSEARCH_SEND_DELAY, WemoRespondToMSearch, 2);
- }
- }
- else if ((EMUL_HUE == Settings.flag2.emulation) &&
- ((request.indexOf(F("urn:schemas-upnp-org:device:basic:1")) > 0) ||
- (request.indexOf(F("upnp:rootdevice")) > 0) ||
- (request.indexOf(F("ssdpsearch:all")) > 0) ||
- (request.indexOf(F("ssdp:all")) > 0))) {
- udp_response_mutex = true;
- TickerMSearch.attach_ms(UDP_MSEARCH_SEND_DELAY, HueRespondToMSearch);
- }
- }
- }
- }
- }
- /*********************************************************************************************\
- * Wemo web server additions
- \*********************************************************************************************/
- const char WEMO_EVENTSERVICE_XML[] PROGMEM =
- "<scpd xmlns=\"urn:Belkin:service-1-0\">"
- "<actionList>"
- "<action>"
- "<name>SetBinaryState</name>"
- "<argumentList>"
- "<argument>"
- "<retval/>"
- "<name>BinaryState</name>"
- "<relatedStateVariable>BinaryState</relatedStateVariable>"
- "<direction>in</direction>"
- "</argument>"
- "</argumentList>"
- "</action>"
- "<action>"
- "<name>GetBinaryState</name>"
- "<argumentList>"
- "<argument>"
- "<retval/>"
- "<name>BinaryState</name>"
- "<relatedStateVariable>BinaryState</relatedStateVariable>"
- "<direction>out</direction>"
- "</argument>"
- "</argumentList>"
- "</action>"
- "</actionList>"
- "<serviceStateTable>"
- "<stateVariable sendEvents=\"yes\">"
- "<name>BinaryState</name>"
- "<dataType>Boolean</dataType>"
- "<defaultValue>0</defaultValue>"
- "</stateVariable>"
- "<stateVariable sendEvents=\"yes\">"
- "<name>level</name>"
- "<dataType>string</dataType>"
- "<defaultValue>0</defaultValue>"
- "</stateVariable>"
- "</serviceStateTable>"
- "</scpd>\r\n\r\n";
- const char WEMO_METASERVICE_XML[] PROGMEM =
- "<scpd xmlns=\"urn:Belkin:service-1-0\">"
- "<specVersion>"
- "<major>1</major>"
- "<minor>0</minor>"
- "</specVersion>"
- "<actionList>"
- "<action>"
- "<name>GetMetaInfo</name>"
- "<argumentList>"
- "<retval />"
- "<name>GetMetaInfo</name>"
- "<relatedStateVariable>MetaInfo</relatedStateVariable>"
- "<direction>in</direction>"
- "</argumentList>"
- "</action>"
- "</actionList>"
- "<serviceStateTable>"
- "<stateVariable sendEvents=\"yes\">"
- "<name>MetaInfo</name>"
- "<dataType>string</dataType>"
- "<defaultValue>0</defaultValue>"
- "</stateVariable>"
- "</serviceStateTable>"
- "</scpd>\r\n\r\n";
- const char WEMO_RESPONSE_STATE_SOAP[] PROGMEM =
- "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
- "<s:Body>"
- "<u:SetBinaryStateResponse xmlns:u=\"urn:Belkin:service:basicevent:1\">"
- "<BinaryState>{x1</BinaryState>"
- "</u:SetBinaryStateResponse>"
- "</s:Body>"
- "</s:Envelope>\r\n";
- const char WEMO_SETUP_XML[] PROGMEM =
- "<?xml version=\"1.0\"?>"
- "<root xmlns=\"urn:Belkin:device-1-0\">"
- "<device>"
- "<deviceType>urn:Belkin:device:controllee:1</deviceType>"
- "<friendlyName>{x1</friendlyName>"
- "<manufacturer>Belkin International Inc.</manufacturer>"
- "<modelName>Socket</modelName>"
- "<modelNumber>3.1415</modelNumber>"
- "<UDN>uuid:{x2</UDN>"
- "<serialNumber>{x3</serialNumber>"
- "<binaryState>0</binaryState>"
- "<serviceList>"
- "<service>"
- "<serviceType>urn:Belkin:service:basicevent:1</serviceType>"
- "<serviceId>urn:Belkin:serviceId:basicevent1</serviceId>"
- "<controlURL>/upnp/control/basicevent1</controlURL>"
- "<eventSubURL>/upnp/event/basicevent1</eventSubURL>"
- "<SCPDURL>/eventservice.xml</SCPDURL>"
- "</service>"
- "<service>"
- "<serviceType>urn:Belkin:service:metainfo:1</serviceType>"
- "<serviceId>urn:Belkin:serviceId:metainfo1</serviceId>"
- "<controlURL>/upnp/control/metainfo1</controlURL>"
- "<eventSubURL>/upnp/event/metainfo1</eventSubURL>"
- "<SCPDURL>/metainfoservice.xml</SCPDURL>"
- "</service>"
- "</serviceList>"
- "</device>"
- "</root>\r\n";
- /********************************************************************************************/
- void HandleUpnpEvent(void)
- {
- AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_BASIC_EVENT));
- String request = WebServer->arg(0);
- String state_xml = FPSTR(WEMO_RESPONSE_STATE_SOAP);
- //differentiate get and set state
- if (request.indexOf(F("SetBinaryState")) > 0) {
- uint8_t power = POWER_TOGGLE;
- if (request.indexOf(F("State>1</Binary")) > 0) {
- power = POWER_ON;
- }
- else if (request.indexOf(F("State>0</Binary")) > 0) {
- power = POWER_OFF;
- }
- if (power != POWER_TOGGLE) {
- uint8_t device = (light_type) ? devices_present : 1; // Select either a configured light or relay1
- ExecuteCommandPower(device, power, SRC_WEMO);
- }
- }
- else if(request.indexOf(F("GetBinaryState")) > 0){
- state_xml.replace(F("Set"), F("Get"));
- }
- state_xml.replace("{x1", String(bitRead(power, devices_present -1)));
- WebServer->send(200, FPSTR(HDR_CTYPE_XML), state_xml);
- }
- void HandleUpnpService(void)
- {
- AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_EVENT_SERVICE));
- WebServer->send(200, FPSTR(HDR_CTYPE_PLAIN), FPSTR(WEMO_EVENTSERVICE_XML));
- }
- void HandleUpnpMetaService(void)
- {
- AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_META_SERVICE));
- WebServer->send(200, FPSTR(HDR_CTYPE_PLAIN), FPSTR(WEMO_METASERVICE_XML));
- }
- void HandleUpnpSetupWemo(void)
- {
- AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_SETUP));
- String setup_xml = FPSTR(WEMO_SETUP_XML);
- setup_xml.replace("{x1", Settings.friendlyname[0]);
- setup_xml.replace("{x2", WemoUuid());
- setup_xml.replace("{x3", WemoSerialnumber());
- WebServer->send(200, FPSTR(HDR_CTYPE_XML), setup_xml);
- }
- /*********************************************************************************************\
- * Hue web server additions
- \*********************************************************************************************/
- const char HUE_DESCRIPTION_XML[] PROGMEM =
- "<?xml version=\"1.0\"?>"
- "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
- "<specVersion>"
- "<major>1</major>"
- "<minor>0</minor>"
- "</specVersion>"
- // "<URLBase>http://{x1/</URLBase>"
- "<URLBase>http://{x1:80/</URLBase>"
- "<device>"
- "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
- "<friendlyName>Amazon-Echo-HA-Bridge ({x1)</friendlyName>"
- // "<friendlyName>Philips hue ({x1)</friendlyName>"
- "<manufacturer>Royal Philips Electronics</manufacturer>"
- "<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>"
- "<modelName>Philips hue bridge 2012</modelName>"
- "<modelNumber>929000226503</modelNumber>"
- "<serialNumber>{x3</serialNumber>"
- "<UDN>uuid:{x2</UDN>"
- "</device>"
- "</root>\r\n"
- "\r\n";
- const char HUE_LIGHTS_STATUS_JSON[] PROGMEM =
- "{\"on\":{state},"
- "\"bri\":{b},"
- "\"hue\":{h},"
- "\"sat\":{s},"
- "\"xy\":[0.5, 0.5],"
- "\"ct\":{t},"
- "\"alert\":\"none\","
- "\"effect\":\"none\","
- "\"colormode\":\"{m}\","
- "\"reachable\":true}";
- const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
- ",\"type\":\"Extended color light\","
- "\"name\":\"{j1\","
- "\"modelid\":\"LCT007\","
- "\"uniqueid\":\"{j2\","
- "\"swversion\":\"5.50.1.19085\"}";
- const char HUE_GROUP0_STATUS_JSON[] PROGMEM =
- "{\"name\":\"Group 0\","
- "\"lights\":[{l1],"
- "\"type\":\"LightGroup\","
- "\"action\":";
- // "\"scene\":\"none\",";
- const char HueConfigResponse_JSON[] PROGMEM =
- "{\"name\":\"Philips hue\","
- "\"mac\":\"{ma\","
- "\"dhcp\":true,"
- "\"ipaddress\":\"{ip\","
- "\"netmask\":\"{ms\","
- "\"gateway\":\"{gw\","
- "\"proxyaddress\":\"none\","
- "\"proxyport\":0,"
- "\"bridgeid\":\"{br\","
- "\"UTC\":\"{dt\","
- "\"whitelist\":{\"{id\":{"
- "\"last use date\":\"{dt\","
- "\"create date\":\"{dt\","
- "\"name\":\"Remote\"}},"
- "\"swversion\":\"01041302\","
- "\"apiversion\":\"1.17.0\","
- "\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
- "\"linkbutton\":false,"
- "\"portalservices\":false"
- "}";
- const char HUE_LIGHT_RESPONSE_JSON[] PROGMEM =
- "{\"success\":{\"/lights/{id/state/{cm\":{re}}";
- const char HUE_ERROR_JSON[] PROGMEM =
- "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
- /********************************************************************************************/
- String GetHueDeviceId(uint8_t id)
- {
- String deviceid = WiFi.macAddress() + F(":00:11-") + String(id);
- deviceid.toLowerCase();
- return deviceid; // 5c:cf:7f:13:9f:3d:00:11-1
- }
- String GetHueUserId(void)
- {
- char userid[7];
- snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP.getChipId());
- return String(userid);
- }
- void HandleUpnpSetupHue(void)
- {
- AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_HUE_BRIDGE_SETUP));
- String description_xml = FPSTR(HUE_DESCRIPTION_XML);
- description_xml.replace("{x1", WiFi.localIP().toString());
- description_xml.replace("{x2", HueUuid());
- description_xml.replace("{x3", HueSerialnumber());
- WebServer->send(200, FPSTR(HDR_CTYPE_XML), description_xml);
- }
- void HueNotImplemented(String *path)
- {
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s)"), path->c_str());
- AddLog(LOG_LEVEL_DEBUG_MORE);
- WebServer->send(200, FPSTR(HDR_CTYPE_JSON), "{}");
- }
- void HueConfigResponse(String *response)
- {
- *response += FPSTR(HueConfigResponse_JSON);
- response->replace("{ma", WiFi.macAddress());
- response->replace("{ip", WiFi.localIP().toString());
- response->replace("{ms", WiFi.subnetMask().toString());
- response->replace("{gw", WiFi.gatewayIP().toString());
- response->replace("{br", HueBridgeId());
- response->replace("{dt", GetDateAndTime(DT_UTC));
- response->replace("{id", GetHueUserId());
- }
- void HueConfig(String *path)
- {
- String response = "";
- HueConfigResponse(&response);
- WebServer->send(200, FPSTR(HDR_CTYPE_JSON), response);
- }
- bool g_gotct = false;
- void HueLightStatus1(byte device, String *response)
- {
- float hue = 0;
- float sat = 0;
- float bri = 254;
- uint16_t ct = 500;
- if (light_type) {
- LightGetHsb(&hue, &sat, &bri, g_gotct);
- ct = LightGetColorTemp();
- }
- *response += FPSTR(HUE_LIGHTS_STATUS_JSON);
- response->replace("{state}", (power & (1 << (device-1))) ? "true" : "false");
- response->replace("{h}", String((uint16_t)(65535.0f * hue)));
- response->replace("{s}", String((uint8_t)(254.0f * sat)));
- response->replace("{b}", String((uint8_t)(254.0f * bri)));
- response->replace("{t}", String(ct));
- response->replace("{m}", g_gotct?"ct":"hs");
- }
- void HueLightStatus2(byte device, String *response)
- {
- *response += FPSTR(HUE_LIGHTS_STATUS_JSON2);
- response->replace("{j1", Settings.friendlyname[device-1]);
- response->replace("{j2", GetHueDeviceId(device));
- }
- void HueGlobalConfig(String *path)
- {
- String response;
- uint8_t maxhue = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
- path->remove(0,1); // cut leading / to get <id>
- response = F("{\"lights\":{\"");
- for (uint8_t i = 1; i <= maxhue; i++) {
- response += i;
- response += F("\":{\"state\":");
- HueLightStatus1(i, &response);
- HueLightStatus2(i, &response);
- if (i < maxhue) {
- response += ",\"";
- }
- }
- response += F("},\"groups\":{},\"schedules\":{},\"config\":");
- HueConfigResponse(&response);
- response += "}";
- WebServer->send(200, FPSTR(HDR_CTYPE_JSON), response);
- }
- void HueAuthentication(String *path)
- {
- char response[38];
- snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), GetHueUserId().c_str());
- WebServer->send(200, FPSTR(HDR_CTYPE_JSON), response);
- }
- void HueLights(String *path)
- {
- /*
- * http://sonoff/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- */
- String response;
- uint8_t device = 1;
- uint16_t tmp = 0;
- float bri = 0;
- float hue = 0;
- float sat = 0;
- uint16_t ct = 0;
- bool resp = false;
- bool on = false;
- bool change = false;
- uint8_t maxhue = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
- path->remove(0,path->indexOf("/lights")); // Remove until /lights
- if (path->endsWith("/lights")) { // Got /lights
- response = "{\"";
- for (uint8_t i = 1; i <= maxhue; i++) {
- response += i;
- response += F("\":{\"state\":");
- HueLightStatus1(i, &response);
- HueLightStatus2(i, &response);
- if (i < maxhue) {
- response += ",\"";
- }
- }
- response += "}";
- WebServer->send(200, FPSTR(HDR_CTYPE_JSON), response);
- }
- else if (path->endsWith("/state")) { // Got ID/state
- path->remove(0,8); // Remove /lights/
- path->remove(path->indexOf("/state")); // Remove /state
- device = atoi(path->c_str());
- if ((device < 1) || (device > maxhue)) {
- device = 1;
- }
- if (WebServer->args()) {
- response = "[";
- StaticJsonBuffer<400> jsonBuffer;
- JsonObject &hue_json = jsonBuffer.parseObject(WebServer->arg((WebServer->args())-1));
- if (hue_json.containsKey("on")) {
- response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
- response.replace("{id", String(device));
- response.replace("{cm", "on");
- on = hue_json["on"];
- switch(on)
- {
- case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE);
- response.replace("{re", "false");
- break;
- case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE);
- response.replace("{re", "true");
- break;
- default : response.replace("{re", (power & (1 << (device-1))) ? "true" : "false");
- break;
- }
- resp = true;
- }
- if (light_type) {
- LightGetHsb(&hue, &sat, &bri, g_gotct);
- }
- if (hue_json.containsKey("bri")) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
- tmp = hue_json["bri"];
- tmp = tmax(tmp, 1);
- tmp = tmin(tmp, 254);
- bri = (float)tmp / 254.0f;
- if (resp) {
- response += ",";
- }
- response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
- response.replace("{id", String(device));
- response.replace("{cm", "bri");
- response.replace("{re", String(tmp));
- resp = true;
- change = true;
- }
- if (hue_json.containsKey("hue")) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
- tmp = hue_json["hue"];
- hue = (float)tmp / 65535.0f;
- if (resp) {
- response += ",";
- }
- response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
- response.replace("{id", String(device));
- response.replace("{cm", "hue");
- response.replace("{re", String(tmp));
- g_gotct = false;
- resp = true;
- change = true;
- }
- if (hue_json.containsKey("sat")) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
- tmp = hue_json["sat"];
- tmp = tmax(tmp, 0);
- tmp = tmin(tmp, 254);
- sat = (float)tmp / 254.0f;
- if (resp) {
- response += ",";
- }
- response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
- response.replace("{id", String(device));
- response.replace("{cm", "sat");
- response.replace("{re", String(tmp));
- g_gotct = false;
- resp = true;
- change = true;
- }
- if (hue_json.containsKey("ct")) { // Color temperature 153 (Cold) to 500 (Warm)
- ct = hue_json["ct"];
- if (resp) {
- response += ",";
- }
- response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
- response.replace("{id", String(device));
- response.replace("{cm", "ct");
- response.replace("{re", String(ct));
- g_gotct = true;
- change = true;
- }
- if (change) {
- if (light_type) {
- LightSetHsb(hue, sat, bri, ct, g_gotct);
- }
- change = false;
- }
- response += "]";
- if (2 == response.length()) {
- response = FPSTR(HUE_ERROR_JSON);
- }
- }
- else {
- response = FPSTR(HUE_ERROR_JSON);
- }
- WebServer->send(200, FPSTR(HDR_CTYPE_JSON), response);
- }
- else if(path->indexOf("/lights/") >= 0) { // Got /lights/ID
- path->remove(0,8); // Remove /lights/
- device = atoi(path->c_str());
- if ((device < 1) || (device > maxhue)) {
- device = 1;
- }
- response += F("{\"state\":");
- HueLightStatus1(device, &response);
- HueLightStatus2(device, &response);
- WebServer->send(200, FPSTR(HDR_CTYPE_JSON), response);
- }
- else {
- WebServer->send(406, FPSTR(HDR_CTYPE_JSON), "{}");
- }
- }
- void HueGroups(String *path)
- {
- /*
- * http://sonoff/api/username/groups?1={"name":"Woonkamer","lights":[],"type":"Room","class":"Living room"})
- */
- String response = "{}";
- uint8_t maxhue = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
- if (path->endsWith("/0")) {
- response = FPSTR(HUE_GROUP0_STATUS_JSON);
- String lights = F("\"1\"");
- for (uint8_t i = 2; i <= maxhue; i++) {
- lights += ",\"" + String(i) + "\"";
- }
- response.replace("{l1", lights);
- HueLightStatus1(1, &response);
- response += F("}");
- }
- WebServer->send(200, FPSTR(HDR_CTYPE_JSON), response);
- }
- void HandleHueApi(String *path)
- {
- /* HUE API uses /api/<userid>/<command> syntax. The userid is created by the echo device and
- * on original HUE the pressed button allows for creation of this user. We simply ignore the
- * user part and allow every caller as with Web or WeMo.
- *
- * (c) Heiko Krupp, 2017
- *
- * Hue URL
- * http://sonoff/api/username/lights/1/state with post data {"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- * is converted by webserver to
- * http://sonoff/api/username/lights/1/state with arg plain={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- */
- uint8_t args = 0;
- path->remove(0, 4); // remove /api
- uint16_t apilen = path->length();
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_HTTP D_HUE_API " (%s)"), path->c_str());
- AddLog(LOG_LEVEL_DEBUG_MORE); // HTP: Hue API (//lights/1/state)
- for (args = 0; args < WebServer->args(); args++) {
- String json = WebServer->arg(args);
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_HTTP D_HUE_POST_ARGS " (%s)"), json.c_str());
- AddLog(LOG_LEVEL_DEBUG_MORE); // HTP: Hue POST args ({"on":false})
- }
- if (path->endsWith("/invalid/")) {} // Just ignore
- else if (!apilen) HueAuthentication(path); // New HUE App setup
- else if (path->endsWith("/")) HueAuthentication(path); // New HUE App setup
- else if (path->endsWith("/config")) HueConfig(path);
- else if (path->indexOf("/lights") >= 0) HueLights(path);
- else if (path->indexOf("/groups") >= 0) HueGroups(path);
- else if (path->endsWith("/schedules")) HueNotImplemented(path);
- else if (path->endsWith("/sensors")) HueNotImplemented(path);
- else if (path->endsWith("/scenes")) HueNotImplemented(path);
- else if (path->endsWith("/rules")) HueNotImplemented(path);
- else HueGlobalConfig(path);
- }
- void HueWemoAddHandlers(void)
- {
- if (EMUL_WEMO == Settings.flag2.emulation) {
- WebServer->on("/upnp/control/basicevent1", HTTP_POST, HandleUpnpEvent);
- WebServer->on("/eventservice.xml", HandleUpnpService);
- WebServer->on("/metainfoservice.xml", HandleUpnpMetaService);
- WebServer->on("/setup.xml", HandleUpnpSetupWemo);
- }
- if (EMUL_HUE == Settings.flag2.emulation) {
- WebServer->on("/description.xml", HandleUpnpSetupHue);
- }
- }
- #endif // USE_WEBSERVER && USE_EMULATION
|