probonopd / ESP8266HueEmulator

Emulate a Philips Hue bridge running on an ESP8266 using the Arduino IDE.
MIT License
411 stars 93 forks source link

Anyone tried to use ESP8266HueEmulator with Amazon Alexa? #62

Open targence opened 7 years ago

targence commented 7 years ago

I tried to use ESP8266HueEmulator with Amazon Alexa, but Alexa can't find new device. She said: "If you have Philips Hue, press the button on Hue bridge"...

probonopd commented 7 years ago

Sorry, I don't have an Alexa device.

bcatalin commented 7 years ago

I've tried, but I have the same problem with the button. First I need to see why the current code is not working fine with hue mobile app and then I will look to Alexa.

Now is connecting to mobile app but disconnect are reconnect continue and I can see only white color no matter what I select.

jdschuitemaker commented 7 years ago

I am seeing same behavior as @targence and @bcatalin are reporting, but I am using a ReSpeaker (Seeedstudio kickstarter) and not an official Amazon device.

In addition, but not sure if that really is a problem, after registering and logging in on the site meethue.com you can add Hue Bridges. Should this only work for the real Hue's?

probonopd commented 7 years ago

Sorry, but registering and logging in on the site meethue.com is not implemented at all for ESP8266HueEmulator. Not sure if Philips even would allow this for 3rd party devices.

Quanghoster commented 6 years ago

I've been trying to debug this with a packet sniffer and not cracked it yet. I have the hueUpnp python script running on my laptop and this works perfectly. Strange thing I'm seeing is that the ESP doesn't always seem to see the SSDP broadcast from the echo and when it does and the serial log says it's sending a response, I don't see the response on the network. I'm beginning to think it could be more performance related than code. Has anyone else made any progress?

dtila commented 6 years ago

Google Home doesn't find either. I think it's a problem in the discovery since I have tried other hue emulators and they are find by Google.

Quanghoster commented 6 years ago

I've tried two I've found for esp and neither are found by the echo. Not sure I can categorically state it's the esp, but possibly. Ive used wireshark to look at the discovery interaction and it seems that the esp doesn't respond to the echo broadcast. Will have another look this evening

Quanghoster commented 6 years ago

Made some progress... The echo sends a search looking for devices with a name ending "basic:1" the code seems to be case sensitive looking for "Basic:1" as when I changed to match the echo search the esp responded. Still not registering because a put all lights command hasn't yet been implemented in code, which seems to be the next step. I will look in to that next. So some progress

dtila commented 6 years ago

I've had a look also, but without luck since now. Here is a simple example of Hue Emulation (simple anc clear) which works with Alexa and Google Home also https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/emulated_hue/upnp.py

I have tried to replace the device type and the reply is quite similar with what's there. Maybe you have more luck :)

dtila commented 6 years ago

A thing which I have just notice, is that the broadcasted UDN by the ESP code is not a proper guid:

  <UDN>uuid:2f402f80-da50-11e1-9b23-5C:CF:7F:9F:26:6B</UDN>

I will try to fix this and keep you posted !

mariusmotea commented 6 years ago

Check this issue where you have lot of upnp traffic dumps from original hue bridge. Here are my ssdp functions:

def ssdpSearch():
    SSDP_ADDR = '239.255.255.250'
    SSDP_PORT = 1900
    MSEARCH_Interval = 2
    multicast_group_c = SSDP_ADDR
    multicast_group_s = (SSDP_ADDR, SSDP_PORT)
    server_address = ('', SSDP_PORT)
    Response_message = 'HTTP/1.1 200 OK\r\nHOST: 239.255.255.250:1900\r\nEXT:\r\nCACHE-CONTROL: max-age=100\r\nLOCATION: http://' + getIpAddress() + ':80/description.xml\r\nSERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.20.0\r\nhue-bridgeid: ' + (mac[:6] + 'FFFE' + mac[6:]).upper() + '\r\n'
    custom_response_message = {0: {"st": "upnp:rootdevice", "usn": "uuid:2f402f80-da50-11e1-9b23-" + mac + "::upnp:rootdevice"}, 1: {"st": "uuid:2f402f80-da50-11e1-9b23-" + mac, "usn": "uuid:2f402f80-da50-11e1-9b23-" + mac}, 2: {"st": "urn:schemas-upnp-org:device:basic:1", "usn": "uuid:2f402f80-da50-11e1-9b23-" + mac}}
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(server_address)

    group = socket.inet_aton(multicast_group_c)
    mreq = struct.pack('4sL', group, socket.INADDR_ANY)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

    print("starting ssdp...")

    while run_service:
              data, address = sock.recvfrom(1024)
              if data[0:19]== 'M-SEARCH * HTTP/1.1':
                   if data.find("ssdp:all") != -1:
                       sleep(random.randrange(0, 3))
                       print("Sending M Search response")
                       for x in xrange(3):
                          sock.sendto(Response_message + "ST: " + custom_response_message[x]["st"] + "\r\nUSN: " + custom_response_message[x]["usn"] + "\r\n\r\n", address)
                          print(Response_message + "ST: " + custom_response_message[x]["st"] + "\r\nUSN: " + custom_response_message[x]["usn"] + "\r\n\r\n")
              sleep(1)

and for broadcast:

def ssdpBroadcast():
    print("start ssdp broadcast")
    SSDP_ADDR = '239.255.255.250'
    SSDP_PORT = 1900
    MSEARCH_Interval = 2
    multicast_group_s = (SSDP_ADDR, SSDP_PORT)
    message = 'NOTIFY * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nCACHE-CONTROL: max-age=100\r\nLOCATION: http://' + getIpAddress() + ':80/description.xml\r\nSERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.20.0\r\nNTS: ssdp:alive\r\nhue-bridgeid: ' + (mac[:6] + 'FFFE' + mac[6:]).upper() + '\r\n'
    custom_message = {0: {"nt": "upnp:rootdevice", "usn": "uuid:2f402f80-da50-11e1-9b23-" + mac + "::upnp:rootdevice"}, 1: {"nt": "uuid:2f402f80-da50-11e1-9b23-" + mac, "usn": "uuid:2f402f80-da50-11e1-9b23-" + mac}, 2: {"nt": "urn:schemas-upnp-org:device:basic:1", "usn": "uuid:2f402f80-da50-11e1-9b23-" + mac}}
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(MSEARCH_Interval+0.5)
    ttl = struct.pack('b', 1)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
    while True:
        for x in xrange(3):
            sent = sock.sendto(message + "NT: " + custom_message[x]["nt"] + "\r\nUSN: " + custom_message[x]["usn"] + "\r\n\r\n",multicast_group_s)
            sent = sock.sendto(message + "NT: " + custom_message[x]["nt"] + "\r\nUSN: " + custom_message[x]["usn"] + "\r\n\r\n",multicast_group_s)
            #print (message + "NT: " + custom_message[x]["nt"] + "\r\nUSN: " + custom_message[x]["usn"] + "\r\n\r\n")
        sleep(60)

both are in python but you can understand very easy what they are sending. These are made to be identical with original hue bridge (timing is also the same).

Quanghoster commented 6 years ago

I will look into this further over the next few says. I need to set up my raspberry pi 3 as an access point and get it to send all traffic to wireshark first. I'm not seeing everything at the moment. I have downloaded that python emulator and see that works so just need to be able to compare both in full

Quanghoster commented 6 years ago

LightService_cpp.txt Ok, I've made some progress with this . A couple of changes I've made are as follows.

in LightService.cpp For the method void addLightJson(aJsonObject root, int numberOfTheLight, LightHandler lightHandler)

add the following: aJson.addStringToObject(light, "manufacturername", "somevalue"); // type of lamp (all "Extended colour light" for now) aJson.addStringToObject(light, "swversion", "0.1"); // type of lamp (all "Extended colour light" for now)

I also changed the following in void sendJson() from HTTP->send(200, "text/plain", msgStr); to HTTP->send(200, "application/json", msgStr);

I did add a couple of functions that changed the format of the json response for /api/xxxxx/lights calls, to be closer to what I've observed with the echo, but they are no longer referenced. I've left them in the attached in case they are needed at some point. This seems to have solved the discovery issues with echo for me so I'll spend a bit of time looking at the control elements...

probonopd commented 6 years ago

Thanks @Quanghoster :+1: Do you want to send a pull request or do you want me to merge the above?

dtila commented 6 years ago

I can confirm that this works with Google Home also, Thanks for the tip!

From what I have seen, the line

//SSDP.setMessageFormatCallback(ssdpMsgFormatCallback);

has been commented. The message transmission is handled anyway by the SSDP library. However, we may consider in the near future to loop thought 3 item arrays to change the NT and USN like @mariusmotea mentioned in the previous post.

custom_message = {0: {"nt": "upnp:rootdevice", "usn": "uuid:2f402f80-da50-11e1-9b23-" + mac + "::upnp:rootdevice"}, 1: {"nt": "uuid:2f402f80-da50-11e1-9b23-" + mac, "usn": "uuid:2f402f80-da50-11e1-9b23-" + mac}, 2: {"nt": "urn:schemas-upnp-org:device:basic:1", "usn": "uuid:2f402f80-da50-11e1-9b23-" + mac}}

For now it works, and I will create a push request with more additional stuff which I have noticed

Quanghoster commented 6 years ago

Cool. I have been working on testing turning a strip on and off whist monitoring with wireshark. The state is changing correctly but the echo states something went wrong. The only difference between this and a working emulator is that the http connection header is set to keep-alive on the working one and closed on this. Still needs mire investigation...

probonopd commented 6 years ago

Happy to merge one pull request per issue :-)

Quanghoster commented 6 years ago

Sure. Go ahead what we have so far and I will investigate further over the next few days. :)

Quanghoster commented 6 years ago

WireShark is so useful with a Raspberry P set up as a WAP and a sniffer :-) Ok,I now have Alexa responding correctly when changing state of a Led Strip by voice command. There was an issue with the json that was being sent in lightsIdFn().

I've changed this function to call a new function addSingleLightJson instead of addLightJson

void addSingleLightJson(aJsonObject root, int numberOfTheLight, LightHandler lightHandler); void lightsIdFn(WcFnRequestHandler whandler, String requestUri, HTTPMethod method) { int numberOfTheLight = atoi(whandler->getWildCard(1).c_str()) - 1; LightHandler handler = LightService.getLightHandler(numberOfTheLight); switch (method) { case HTTP_GET: { aJsonObject *root; root = aJson.createObject(); addSingleLightJson(root, numberOfTheLight, handler); sendJson(root); break; }

The new function is as follows...

void addSingleLightJson(aJsonObject light, int numberOfTheLight, LightHandler lightHandler) { if (!lightHandler) return; String lightName = "" + (String) (numberOfTheLight + 1);

aJson.addStringToObject(light, "manufacturername", "OpenSource"); // type of lamp (all "Extended colour light" for now) aJson.addStringToObject(light, "modelid", "LST001"); // the model number aJson.addStringToObject(light, "name", ("Hue LightStrips " + (String) (numberOfTheLight + 1)).c_str()); // // the name as set through the web UI or app aJsonObject state; aJson.addItemToObject(light, "state", state = aJson.createObject()); HueLightInfo info = lightHandler->getInfo(numberOfTheLight); aJson.addBooleanToObject(state, "on", info.on); aJson.addNumberToObject(state, "hue", info.hue); // hs mode: the hue (expressed in ~deg182.04) aJson.addNumberToObject(state, "bri", info.brightness); // brightness between 0-254 (NB 0 is not off!) aJson.addNumberToObject(state, "sat", info.saturation); // hs mode: saturation between 0-254 double numbers[2] = {0.0, 0.0}; aJson.addItemToObject(state, "xy", aJson.createFloatArray(numbers, 2)); // xy mode: CIE 1931 color co-ordinates aJson.addNumberToObject(state, "ct", 500); // ct mode: color temp (expressed in mireds range 154-500) aJson.addStringToObject(state, "alert", "none"); // 'select' flash the lamp once, 'lselect' repeat flash for 30s aJson.addStringToObject(state, "effect", info.effect == EFFECT_COLORLOOP ? "colorloop" : "none"); aJson.addStringToObject(state, "colormode", "hs"); // the current color mode aJson.addBooleanToObject(state, "reachable", true); // lamp can be seen by the hub aJson.addStringToObject(root, "type", "Extended color light"); // type of lamp (all "Extended colour light" for now)

aJson.addStringToObject(light, "swversion", "0.1"); // type of lamp (all "Extended colour light" for now) aJson.addStringToObject(light, "type", "Extended color light"); // type of lamp (all "Extended colour light" for now) aJson.addStringToObject(light, "uniqueid", ((String) (numberOfTheLight + 1)).c_str());

}

Here is the updated cpp. I must figure out how to deal with puah/pull, lol LightService_cpp.txt

Quanghoster commented 6 years ago

Now have the echo turning on an off the EP-12F built in LED by command. Mine is on pin 2 rather than LED_BUILTIN, but works a treat. I've been making some changes to use the WS2812FX library rather than the NeoPixelBus as I find it a lot easier to work with

probonopd commented 6 years ago

Just click the "Edit" button on the GitHub page for that file, make your changes, click the button at the bottom of the page. When asked, create a Pull Request and send it. Should not take longer than a minute to do.

This way, your work will receive the proper attribution in the project history. Thank you.

Quanghoster commented 6 years ago

Ok, Think I've done that now using GitHub Desktop

probonopd commented 6 years ago

So far I don't see anything - please do a "git commit" and a "git push" in GitHub Desktop.

Quanghoster commented 6 years ago

permissions?

fighterxxl commented 6 years ago

Wow guys this is just great! Making work this projekt with echo would be awsome. I own a echo dot for a few days and it was fun to make the smart home Integration with the fauxmo lib for my 433mhz power outlets. Im looking forward to do a simalar thing my colored led strip. I think this project would be a better choice than trying to write a custom alexa skill. Can you please tell me if there is something special for me to know about the echo discovery? Or will this work out of the box with your enhancement? I am really looking forward to try this :-)

cheers FighterXXL

Quanghoster commented 6 years ago

Hi @fighterxxl . We have some changes we are checking in at the moment that sort out discovery with Alexa. I have an ESP-12E here that Alexa sees and can turn on and off the onboard LED, so yes it's pretty well good to go with these changes. Thanks to @probonopd for a great project

Quanghoster commented 6 years ago

finally have some working code using NeoPixelBus. I've derived a new class from Lighthandler called Echohandler which can be used in place of Pixelhandler. it allows a neopixel strip to be turned on with some very basic animation and turned off with the echo. need contribute access to load the enhancements... :)

fighterxxl commented 6 years ago

Hi @Quanghoster, I had a bit trouble yesteday building this Project. Using your last post LigthService.cpp I wasn't able to use the ESP8266 with alexa. But the alexa device discovery triggerd some json on the serial :) I am curious about your EchoHandler and I like to test that. If I am too stupid to use it with alexa, could you provide a few instructionlines how to use it with alexa? And of cource thanks @probonopd for this project :)

cheers FighterXXL

fighterxxl commented 6 years ago

Hi *, got it to work. But its too early for me to use. Alexa don't know color commands in my country. Simple on and off works but colors won't -.- Too bad, maybe in a few month...

cheers FighterXXL

Quanghoster commented 6 years ago

I'm working on a way to simulate colour changes or animations.  We can use the individual light definitions to select animations or colours on a single strip On 6 Oct 2017 22:09, fighterxxl notifications@github.com wrote:Hi *, got it to work. But its too early for me to use. Alexa don't know color commands in my country. Simple on and off works but colors won't -.- Too bad, maybe in a few month... cheers FighterXXL

—You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub, or mute the thread.

probonopd commented 6 years ago

@Quanghoster and @fighterxxl thanks for the progress you are making. Please do send GitHub pull requests. You don't need special permissions for this.

https://help.github.com/articles/creating-a-pull-request/

Quanghoster commented 6 years ago

I've figured out how to do one now I've forked your project. Lol. I hope to look in to Alexa color changes this week. I can turn off and on and dim but pretty sure you can change colours now. But not with what we have here atm

Quanghoster commented 6 years ago

OK, I've been busy investigating colour change support for alexa and, as I understand it, we will need a different approach. it looks like alexa doesn't do this via the local rest api on the hub. it does it via the smart home skill. So. to be able to do this, and possibly set scenes, we need to use a cloud service as skills can't access the hub. I have created an initial alexa smart home skill to test with and am looking at interfacing with aws iot. From there I can build in mtqq connectibity to this project to support the various calls. aws-iot seems a natural selection for this because you need an oauth provider to Link accounts to a smart home skill. it's more natural to use a smart home skill than a custom skill as you don't need an activation word. I'll post more on this in a few days

Quanghoster commented 6 years ago

I've just submitted a pull request which includes a fix for Alexa discovery.

I have also made some progress with getting a test sketch working to connect to to AWS IoT with an MQTT client to subscript to shadow update messages along with an initial node.js alexa smarthome skill which will eventually connect to IoT aswell. A little early to post anything just yet, but it's now coming along nicely. Hopefully some further updates in a week or so

Quanghoster commented 6 years ago

I'm getting some wierdness happening since I resynced my repository. Discovery is working with the echo but there seems to be a problem with the /state/ command, both with the echo and an android Hue app. Debugging the parsing with the webserver shows the following. It looks like the args are not being processed correctly and I end up with HTTP->arg("plain").c_str() being empty. Is anyone else seeing this problem?

Andy.

method: PUT url: /api/null/lights/1/state search: headerName: Content-Type headerValue: application/x-www-form-urlencoded headerName: User-Agent headerValue: Dalvik/2.1.0 (Linux; U; Android 6.0.1; D6503 Build/23.5.A.1.291) headerName: Host headerValue: 10.10.100.17 headerName: Connection headerValue: Keep-Alive headerName: Accept-Encoding headerValue: gzip headerName: Content-Length headerValue: 20 args: {"on":true,"bri":90} args count: 1 pos 0=@ -1 &@ -1 arg missing value: 0 args count: 0 Plain: {"on":true,"bri":90} Request: /api/null/lights/1/state Arguments: {"on":true,"bri":90} 44182 [{"error":{"type":2,"address":"/api/null/lights/1/state","description":"Bad JSON body in request"}}]

Quanghoster commented 6 years ago

Strange, It seems the echo is sending as content-type "x-www-form-urlencoded" which seems to be related to the issue. I'm not sure that make sense, but it works for others.

The following works ok from another 3rd party Windows 10 app.

New client method: PUT url: /api//lights/1/state search: headerName: Content-Length headerValue: 19 headerName: Content-Type headerValue: application/json; charset=utf-8 headerName: Host headerValue: 192.168.1.22 headerName: Connection headerValue: Keep-Alive args: Plain: {"bri":1,"on":true} Request: /api//lights/1/state Arguments: arg0plain 325550 [{"success":{"/lights/1/state/bri":1}},{"success":{"/lights/1/state/on":true}}]

Quanghoster commented 6 years ago

Ok, I've figured this out. I'm not sure why the echo is posting with Content-Type of application/x-www-form-urlencoded, but the webserver doesn't behave as expected with this and therefore doesn't fill the plain variable. I've hacked Parsing.cpp at line 166 to force isEncoded to false to avoid the problem for now. This game me a successful response to /lights/n.

  if (headerName.equalsIgnoreCase("Content-Type")){
    if (headerValue.startsWith("text/plain")){
      isForm = false;
    } else if (headerValue.startsWith("application/x-www-form-urlencoded")){
      isForm = false;
      isEncoded = false;// <--- HACK changed from true
    } else if (headerValue.startsWith("multipart/")){
      boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
      boundaryStr.replace("\"","");
      isForm = true;
    }
  } else if (headerName.equalsIgnoreCase("Content-Length")){
    contentLength = headerValue.toInt();
  } else if (headerName.equalsIgnoreCase("Host")){
    _hostHeader = headerValue;
  }
}

This then showed up the json response format for /lights/n/state which I'll shortly post a PR for.

Andy.

MopheusDG commented 6 years ago

Actually I used this with Alexa. As soon as the ESP8266 booted, did a search in Alexa and it found the 2 strips without issues. I think it could be more useful if the library could provide an easier way to define Lamps names, cause I have to edit the other files to do it. Maybe we should build something more like the FauxmoESP library where you define each "device" easily, but it's just an idea of course... I still need to take a deeper look into this code and so far, works fine except when you want to create a Room setup, but Alexa doesn't need that, it just sees the lamps. At least on my Echo Dot v1.