TomFaulkner / SenseMe

Python Library for Haiku SenseMe app controlled fans/lights
GNU General Public License v3.0
21 stars 10 forks source link

Chat #1

Closed dcplaya closed 6 years ago

dcplaya commented 7 years ago

I have a apostrophe in my fan name, when I run the test run.py program, I get

AttributeError: 'NoneType' object has no attribute 'group'

I am thinking it is because of the apostrophe. I will continue testing/trying to fix it.

Edit:

It definitely is an issue with apostrophes. I changed the discover to discover a specific device by

fan = SenseMeFan('10.10.1.117', 'Drew\'s Room Fan')

But I had to include the \ for it to work properly.

dcplaya commented 7 years ago

Fixed it, Haiku has changed the way they report the names of the devices. Below should fix it I think for all devices (L Series fan, switches, and lights I think)

res = re.match('\((.*);DEVICE;ID;(.*);(.*),(.*)\)',self.details) self.name = res.group(1) self.mac = res.group(2) self.model = res.group(3) self.series = res.group(4)

TomFaulkner commented 7 years ago

Interesting. I haven't used this since I posted it. I like that you are working Plex integration in. I hadn't thought about that, but I may very well use what you have if you get it working.

dcplaya commented 7 years ago

You are more than welcome to grab what I have too. I finally was able to get the JSON parsed correctly today. There is still a lot of work I want to do with Haiku side (support more devices, and also the discovery service only shows the 1st device, not all devices, plus a few other improvements). I'm also hoping BigAssFans is willing to help with a list of commands maybe.

Eventually it would be nice to have multiple lights controlled via multiple clients with a single config file or something. I'm not an expert programmer so it might be dirty.

TomFaulkner commented 7 years ago

Supporting multiple devices shouldn't be an issue. A list of SenseMe objects would work. Device discovery is an interesting problem though. I think I could discover multiple devices without much of an issue. (I only have the one fan though, so can't test.) But, then you would want an object for each. Discovery should then be it's own class or at least a separate function.

I think the way I would handle it would be to have discovered devices written to a file and that file being read on app startup to skip discovery. This would assume static IPs. And, really, I think a static IP is the best way to handle these devices.

I spoke to support about the issues that lead to this script, they were excited to hear what I had done with it, though I didn't ask for the commands.

I've also considered using a Raspberry Pi as a fan or light controller and writing a listener for that. But, I haven't gone that route yet. Then the Haiku app could talk to devices that pretend to be SenseMe devices. :)

I just checked my fan, it identifies itself as (b'(Living Room Fan;DEVICE;ID;20:F8:5E:D8:A2:74;FAN,HAIKU,HSERIES)', ('192.168.1.50', 31415)). It has the full fan and light feature set. So, the checks for series should be at least inclusive of H. I think a better check would be for the FAN after the MAC address. (List of fans: https://www.haikuhome.com/learn-more-haiku-fans)

You also mentioned light switches. I didn't realize they made those. I don't see them on the site anyway. Though, again, I think it would be trivial to make a Pi into a SenseMe light switch.

dcplaya commented 7 years ago

The switches identify as SWITCH I think (can't remember for sure). I wasnt sure if all fan series identified the same since all I have is an L Series right now. I do have their LED lights but have no gotten them to identify their selves yet. I definitely need to sniff packets to see.

Their support is really helpful. I had/have issues with my fan working in the app and have been working with their support for a few weeks now to figure out what's wrong. I think it was a setting on my router (igmp proxy) but am not 100% sure on that yet.

A pi connected to a cheaper dimmable light switch that is Bluetooth or one of those IoT protocols would be a cheaper way to make a light. Although I'm sure Haiku would prefer we don't do that.

TomFaulkner commented 7 years ago

I'm a network engineer, we use IGMP for stuff at work. I seriously doubt the IGMP proxy is relevant. For IP video IGMP is used to monitor who needs to receive multicast feeds. The issue I had that lead to writing this to troubleshoot the app, which I initially thought was incompatibility with Android N developer preview, was that the router was set to isolate my phone for some reason. Many APs or wireless routers may prevent wireless devices from accessing the network.

To troubleshoot I installed a packet sniffing app on my phone and on my tablet and let them watch one another run the app. You'll be surprised in how frequently the app does discovery. I can't imagine it is good on the battery to leave that app running.

I was thinking Pi + relay wired in before an outlet. Yeah, they probably wouldn't want that, but it's taking advantage of a simple protocol, and it saves writing your own app. Surely I wouldn't sell such a thing. :)

I have some bookshelf lights that would be perfect for this. If you get the Plex thing going I might just write the server for a switch and do exactly this. Or, maybe an even simpler protocol, we'll see.

dcplaya commented 7 years ago

The flask Plex relay is all but working. I just don't have the commands in there to dim the lights. I just have a comment placeholder in there for now. There are some hardcoded IPs player IDs that need to be pulled out and put into a config file.

TomFaulkner commented 7 years ago

If you don't have a format for that config picked out, I would suggest configObj. https://github.com/DiffSK/configobj

I'll have to give it a try this weekend. Is this controlled through the Plex app? I've not looked at integrating anything with Plex.

dcplaya commented 7 years ago

The newest version of Plex released "webhooks". There is a Plex webpage for it. You have to be a Plex pass user.

dcplaya commented 7 years ago

I now have sensemefan detecting and storing the info for multiple devices now (still only fans and SenseMe switches, no lights yet). The biggest issue now is that my fan isn't always detected, maybe 1 out of every 5 tries it broadcasts back data.

Also, right now the dictionary isn't available outside of the class yet, I havent done that. But if you want to look, its here

I also started using the configobj library. Ideally I would like to have the config require only the name of the fan and it will grab the rest of the data it needs from discovery.

TomFaulkner commented 7 years ago

I don't think you want to use the global for the device list. Globals act funny when called in modules, and that you have the class returning it makes for more problems.

I think a discovery class that returns a device list would probably be a better option. That way you can have one instance of the SenseMeFan class per device. This way the SenseMeFan class can remain simple and do one thing.

Or, if you prefer to store only the list of names, there could be a discovery that is called during the class init that discovers that specific device, and then you would still have an instance per device.

If it is helpful, you could also have a SmartHome class that houses the smart devices, though I think this might be a bit overkill.

I would also keep the smart stuff out of the SenseMeFan class, again, to keep it simple. So when Plex starts playing it calls a set_the_mood method of another class that knows what to do with each of the SenseMe (and other?) devices.

dcplaya commented 7 years ago

I don't think you want to use the global for the device list.

I figured that was a bad way to do it but wasn't sure what other way there was and I also wanted to make sure it was working really fast. I am not the best programmer in general and this is really the 1st time I have dove into python coding minus some small things here and there. Like I said before, feel free to fork or submit pull requests even if its just to clean code up.

I also did some packet sniffing for the commands for their new lights but as far as I could tell, it is now going through some external server. Only command I could see that was broadcast was the command to ask for firmware version. The rest were all GSVP protocol according to wireshark

TomFaulkner commented 7 years ago

I also did some packet sniffing for the commands for their new lights but as far as I could tell, it is now going through some external server. Only command I could see that was broadcast was the command to ask for firmware version. The rest were all GSVP protocol according to wireshark

If that is the case, I don't expect I will be buying anything else from them. That these things can be controlled with our own scripts is quite a selling point to me.

I have some thoughts on how to future-proof the code to support more and varying devices using a few objects, but if their new devices won't work with it, I guess there isn't much of a point to it.

If you're interested though, I was thinking a base SenseMe class that served as a framework for a Fan class and a Light class to inherit from. A fan with lights then could inherit from both classes and not have to really be programmed at all. This would make room for different types of fans or lights in the future as well as allow for light switches to be programmed with little effort, or perhaps if they branch into other home automation fields.

Also, as I mentioned before, I think discovery probably needs to be a separate object, or it could be a method called on init to discover an individual device based on the parameters given. This way each device is an instance of an object. State data is then trivial to maintain as well.

To use this library you would then probably have a list or dictionary of devices and could iterate over them and such. Or, and this would be another improvement, I would think, a SmartHome class that holds all of the smart device objects. It would probably be used for discovery and would create the other objects as they are discovered or pulled from a config.

TomFaulkner commented 7 years ago

I emailed Haiku Home support and sent them this thread. Here is their response:

Our devices still use UDP and TCP protocol as well as Cloud updates. UDP is used for discovery and groups, and TCP is used for commands. Cloud updates, Alexa and Nest/Ecobee(if in follow tstat mode) use an external server.

If they have indeed switched to TCP for commands, more sniffing would be in order.

dcplaya commented 7 years ago

Ah that's why I didn't see it. I was filtering just UDP protocol. I can do some more sniffing Sunday. It's pretty nice Haiku was so willing to give out that info too!

dcplaya commented 7 years ago

I have separated out the discovery like you suggested (v0.21 branch) and got rid of that global device_list

TomFaulkner commented 7 years ago

Been awhile. Have you done anything more with this project?

dcplaya commented 7 years ago

It's not the cleanest by far but I have a basic webpage that shows all detected devices. The devices are stored in a sqlite database and is updated if you request it via the webpage (this gets by the terrible detection I've been seeing). You can also control the lights and fan via the webpage by clicking on the device. My next addition will be the ability to delete devices from the database and manually add/edit ones already in it. And then of course pretty up the webpage. After that I need to add a settings page to allow my Plex server to hook into it so I can have it automatically dim the lights during playback and stuff.

TomFaulkner commented 7 years ago

Nice. I didn't see that in your source. Maybe I missed it? Or have you not posted it? (Which is fine.) But, I'm kind of bored tonight and too sick to do anything too useful, so I thought I might give the class a little love.

dcplaya commented 7 years ago

I'm pretty sure GitHub has my newest source (I think). It may not be clear in the source. I create the database on the fly if it isn't detected. I think it actually fails the 1st time if it's not detected. I'm not sure why yet, it's a low priority bug haha

TomFaulkner commented 7 years ago

I've been playing with this for a little bit tonight. I have yet to get my fan to discover. Not sure if due to my Chromebook not listening properly or what. It can control the fan at least.

Is the discovery function still working for you? I noticed in your script you are going with hard-coded details.

dcplaya commented 7 years ago

I don't remember it off the top of my head. I can look at it tomorrow, if you think there is a better way we can merge it.

TomFaulkner commented 7 years ago

I pushed my changes. I changed everything to proper PEP8 guidelines, so a lot of names have changed for functions, and even the class name to match the project name here and that it supports more than just fans.

I took out the LSeries checks you had, mine is an HSeries and works. I took out the fan checks too, lights should safely ignore commands that only apply to fans. If they don't, I have a better way in mind anyway.

I think the discovery issue is the chromebook. My phone immediately receives a response when sending the command.

I know how to handle discovery of multiple objects properly now, and I may get to adding that tomorrow. It needs to be a separate function or class that does it, then creates the objects based on what it's found. It would then return a list or whatever of objects.

I'll probably write it, but I don't have a way to test, since I only have one.

dcplaya commented 7 years ago

I have no problem testing it with multiple devices. Also, all the database stuff is in my v0.22 branch (not sure if you overlooked it). Ill work on merging in your changes so it adheres to PEP8 more too, shouldnt be too bad, just changing function names.

TomFaulkner commented 7 years ago

So, the discovery system doesn't work on a Chromebook.

Anyway, since I know that for sure now, I may get discovery rewritten to discover multiple devices simultaneously.

TomFaulkner commented 7 years ago

I wrote a Discover class and in it a discover module. I tested it on one device and it seems to work fine. If you are able to test it on multiple that would be cool.

Usage: from Senseme.senseme import Discovery fans = Discovery().discover() # optional params: devices_to_discover = None, time_to_wait = None

fans should then be a list of SenseMe devices. If no parameters are given it seems to take about 5 seconds before the timeout occurs and the recvfrom is done blocking so execution can continue.

dcplaya commented 7 years ago

Awesome, the way I do it now was definitely not the most efficient. I will test this out next week, I have been traveling for work and on top of all that I have been sick so I haven't had much free time.

dcplaya commented 7 years ago

It seems to be detecting multiple devices but if it expects to detect 3 but only detects less than 3, it crashes instead of of gracefully returning the shortened list of devices with the following error.

socket.timeout: timed out

Your code is much more elegant than mine so I would definitely want to use it. I also cannot seem to get it to print the value of the returned devices list so I can verify what's in it (This is probably just me being dumb and missing something simple though)

TomFaulkner commented 7 years ago

You used this: from Senseme.senseme import Discovery fans = Discovery().discover() # optional params: devices_to_discover = None, time_to_wait = None

Not fan.discover(), right? I'll actually put a warning on this function to prevent it's use. I also noticed that I have get_ functions, which shouldn't be an exposed thing.

As for accessing things, the Discovery class is returning a list of SenseMe devices.

from Senseme.senseme import Discovery fans = Discovery().discover() # optional params: devices_to_discover = None, time_to_wait = None for fan in fans: fan.light_toggle()

or this fan[0].light_toggle()

Thinking about this now, it might be better to return a dictionary of devices with the key being the device name.

When I get that done it will be: fans['Living Room Fan'].light_toggle()

The for loop above would also work for the dictionary. I'll keep the list return and have an optional dictionary return instead.

I'll also make the imports less redundant soon. :)

TomFaulkner commented 7 years ago

What version of Python are you using? Since as far as I know you are the only user, if you are using 3.6 or are able to move to 3.6, I will use some 3.6 features.

Also, this:

It seems to be detecting multiple devices but if it expects to detect 3 but only detects less than 3, it crashes instead of of gracefully returning the shortened list of devices with the following error.

socket.timeout: timed out

Your code is much more elegant than mine so I would definitely want to use it. I also cannot seem to get it to print the value of the returned devices list so I can verify what's in it (This is probably just me being dumb and missing something simple though)

Can you give me the traceback that you get when this occurs? I have one device, when running discover it happily returns the one device for me. Also, please use my latest updates so the line numbers line up.

I've done some clean-up, (and noticed where more is necessary...) and I added a repr and str method that should make your life easier. That is, if you do print(fans[0]) you will get something more helpful than the memory location and type of object. If you do print(repr(fans[0])) you will get more details.

TomFaulkner commented 7 years ago

I forgot to commit when I had made a few changes, then I realized a much better way to handle reporting and setting speed/light level. So, I think the code will be much better.

Methods have been renamed again, but the public ones should mostly be finalized.

TomFaulkner commented 7 years ago

Did some more work on this today, in a new branch. I noticed you had put in your readme about wanting some of the other details that I haven't provided. You should be able to see all data about the fan if you run the examples/get_all.py I posted. The plan is to have a _get_all method to provide this in an accessible format, but I'm going to have to think about how to parse that.

"Allow user to manually enter IP/MAC of device and have the app go out and pull the remaining info it needs from the device, then store this in the database"

The now renamed discover_single_device method should do the lookup for you if you provide the IP. However, looking at it now, I just realized that the method doesn't do what I was thinking it did.

I may work on that next week, but, if the command sent is changed to have the device name instead of ALL it would work, as that instructs only that device to reply. If you instead changed the s.sendto line to send to a specific IP instead of the packet only goes to that IP and only that device will reply.

It is also possible to send only to a specific MAC, however, I'm not sure how to get sockets to do that. You would use the name ALL with IP and specify the MAC to send to. But, again, I haven't looked at how to do that in the socket library.

TomFaulkner commented 7 years ago

A way to open a port and monitor it for all BROADCAST messages so I do not have to constantly spam devices for their status

That used to be how the device communicated, as I understand it that is no longer the case with the firmware update. The Android app does, or at least did, exactly as you describe, in addition to monitoring UDP broadcasts. It's on a timer of somewhere around 20 seconds, I believe. You can, however, open a TCP connection in a separate thread to each device and I believe it sends status changes out.

My get_all method would probably be the ideal way to do the spamming if you choose to go that route. Once it is finished, that is.

Store all detected devices in a database and update that database when the refresh button is pushed on webpage. This will hopefully get around the fact that many times not all devices are detected

Allow user to manually enter IP/MAC of device and have the app go out and pull the remaining info it needs from the device, then store this in the database

That should be easy enough to do. Detection is really not necessary if the IP address and name are known. A static IP in your router for the devices makes this easy. If you don't want to do static IPs,, the discover_single_device method, once fixed, should work by name. The ideal if you really wanted to make sure you could change the name and have a dynamic IP would be to go based on MAC. But, that's more work. :)

Sniff more commands such as hue of the lights, motion settings and more

If you do this, let me know what you find. I only have the one device, but I would include it. I have the commands for motion settings in a note, I'll post it, probably in the wiki or as an issue.

Log data, fan/light status, motion detection, temperature, and whatever else I can grab. Store this in a database too so I can look at histories

Love that idea. Would be cool for a dashboard. I have an app that does that for my Nest thermostat.