Code Monkey home page Code Monkey logo

senseme's Introduction

Code style: black

SenseMe

Python Library for Haiku SenseMe app controlled fans/lights

This library is useful to control Haiku SenseMe fans with light kits. I would expect it will probably also work with their lights.

It might also be useful for controlling DIY projects as the protocol is very simple and would be easy to clone. And, if you were to use their API the Android and iOS apps may work to control DIY devices. A suggested idea would be to add an Arduino or Raspberry Pi and a relay or two to your own fan and use this or the Haiku Home app to control them. I've read that doing this sort of thing with a resistor is a dangerous idea, so don't do that.

Sniffing the packets and documenting the protocol were the work of Bruce. Much of the code was based on his work on making an Indigo plugin

See Issues for known issues or if you want to contribute but don't know where to start. Some easy fixes are labeled.

I am not affiliated with Haiku Home or Big Ass Fans. Their support rep said this project seemed cool in it's infancy, and they even answered a technical question regarding the protocol for me, so hopefully they still approve.

Future

Some ideas for future related projects and features:

  1. Plex plugin (dim the fans when the movie starts and light up when it is paused or ends? Yes, please!)

  2. Alexa / Google Home plugins (see REST API below)

  3. Store information in a database (sqlite, or even json, would be fine) rather than discovering each time.

  4. Track usage and temperatures

  5. Other automation system plugins

  6. REST API backend with a CLI client (a basic direct to fan command line client was added in 0.1.3)

  7. React or other web based client for the above API

  8. Docker image for the above

  9. Use protocol to control other devices by attaching an Arduino or Pi

  10. Discover the rest of the protocol. (Run strings on the apps and some packet sniffing.)

  11. More examples

  12. Command Line Client Basic client added in 0.1.3, see SenseMe CLI below

  13. Unit Tests

Usage

from senseme import discover
# discover devices, returns list of SenseMe devices
devices = discover()
fan = devices[0]
repr(fan)

SenseMe(name='Living Room Fan', ip='192.168.1.50', model='FAN,HAIKU', series='HSERIES', mac='20:F8:5E:E3:AB:00')

# Statically assign the fan? Probably not, but you would do it this way:
from senseme import SenseMe
fan = SenseMe('192.168.1.50', 'Living Room Fan', model='FAN')
# or, this might be easier
fan = SenseMe(name="Living Room Fan")

Control the fan:

# Turn the light off / on
fan.light_powered_on = False
fan.light_powered_on = True
# or, if you just want to toggle it
light_status = fan.light_toggle()

# Increase light level by 2 levels
fan.inc_brightness(2)

# Get Light level
print(fan.brightness)

# Fan Speeds
fan.fan_powered_on = True
fan.fan_powered_on = False
print(fan.speed)

# whoosh mode
fan.whoosh = True

# want an increasing light effect? Do this.
# But, really, probably don't, I don't think Haiku intended strobe effects.
# I'm not responsible if you make a strobe light and break the fan or worse
for intensity in range(1,16):
    fan.brightness = intensity
    time.sleep(1)

# export details to json / xml / str (as `str(dict)`)
fan.json
fan.xml
fan.dict  # nested dict
fan.flat_dict  # flattened

# Listen for broadcasts, useful for debugging,
# wouldn't suggest using it for anything else
fan.listen()

SenseMe CLI

In version 0.1.3 a script was added to control HaikuHome SenseMe devices from a command line.

Set the following environment variables:

SENSEME_DEFAULT_FAN_IP
SENSEME_DEFAULT_FAN_NAME

Then run senseme_cli with one of the following options.

fan x           - set speed to x (0-7)
fan on|off      - fan on/off
light x         - set light brightness to x
light on|off    - light on/off
whoosh [on|off] - whoosh mode on/off

Requirements

Python 3.6 is required to use this library. SenseMe devices communicate on UDP port 31415 using broadcast and unicast traffic. Any firewall will have to allow this through.

Concurrent fan control and real-time data collecting

Mike Lawrence made a fork that improves upon some of the limitations in this library and makes use of concurrency to talk to multiple devices simultaneously. I haven't use his library, but it looks promising.

senseme's People

Contributors

dependabot-preview[bot] avatar mattgam avatar tomfaulkner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

senseme's Issues

Flask example: Leads to occasional 500 + Traceback

When clicking Toggle Fan in the example Flask app there is a fairly high chance that the call to fan.speed may return 'OFF' rather than an integer for the speed. I haven't been able to replicate this outside of the flask app. Perhaps it is a function of how little time is between the .fan_toggle and .speed request.

Or, in thinking about this, it is more likely that because the library doesn't listen for responses to commands and then the .speed is catching the response.

For now there is exception handling in the .speed method.

[2018-03-06 20:32:40,496] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
  File "/home/tom/Programming/Python/SenseMe/.venv/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/tom/Programming/Python/SenseMe/.venv/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/tom/Programming/Python/SenseMe/.venv/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/tom/Programming/Python/SenseMe/.venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/home/tom/Programming/Python/SenseMe/.venv/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/tom/Programming/Python/SenseMe/.venv/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "flask_app.py", line 17, in index
    flask.flash(str((fan.speed, fan.brightness)))
  File "/home/tom/Programming/Python/SenseMe/senseme/senseme.py", line 100, in speed
    return int(speed)
ValueError: invalid literal for int() with base 10: 'OFF'
127.0.0.1 - - [06/Mar/2018 20:32:40] "GET / HTTP/1.1" 500 -

Where to begin? Would like to get running in Flask.

Is there any kind of guide to get this running in flask? I have 5 fans that I want to control and I would like to statically assign them. I know the mac addresses and names. I have a Synology NAS that I am running docker on and I have Flask already running in a container. I am launching flask_app.py, but when I laugh the webpage, I get a Internal Server Error message. I am sure it is not discovering my fans. Any pointers would be much appreciated.

doesn't work with python 2.7?

installation:

pi@62fib-raspberry:~/SenseMe/SenseMe $ python setup.py install
/usr/lib/python2.7/distutils/dist.py:267: UserWarning: Unknown distribution option: 'python_requires'
  warnings.warn(msg)
running install
running bdist_egg
running egg_info
creating SenseMe.egg-info
writing SenseMe.egg-info/PKG-INFO
writing top-level names to SenseMe.egg-info/top_level.txt
writing dependency_links to SenseMe.egg-info/dependency_links.txt
writing manifest file 'SenseMe.egg-info/SOURCES.txt'
reading manifest file 'SenseMe.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching '*.txt' under directory 'docs'
writing manifest file 'SenseMe.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-armv7l/egg
running install_lib
running build_py
creating build
creating build/lib.linux-armv7l-2.7
creating build/lib.linux-armv7l-2.7/senseme
copying senseme/__init__.py -> build/lib.linux-armv7l-2.7/senseme
copying senseme/senseme.py -> build/lib.linux-armv7l-2.7/senseme
copying senseme/known_attribs.py -> build/lib.linux-armv7l-2.7/senseme
creating build/lib.linux-armv7l-2.7/senseme/lib
copying senseme/lib/__init__.py -> build/lib.linux-armv7l-2.7/senseme/lib
copying senseme/lib/mwt.py -> build/lib.linux-armv7l-2.7/senseme/lib
copying senseme/lib/perpetualtimer.py -> build/lib.linux-armv7l-2.7/senseme/lib
copying senseme/lib/background_monitor.py -> build/lib.linux-armv7l-2.7/senseme/lib
copying senseme/lib/xml.py -> build/lib.linux-armv7l-2.7/senseme/lib
creating build/lib.linux-armv7l-2.7/bin
copying bin/__init__.py -> build/lib.linux-armv7l-2.7/bin
creating build/bdist.linux-armv7l
creating build/bdist.linux-armv7l/egg
creating build/bdist.linux-armv7l/egg/bin
copying build/lib.linux-armv7l-2.7/bin/__init__.py -> build/bdist.linux-armv7l/egg/bin
creating build/bdist.linux-armv7l/egg/senseme
copying build/lib.linux-armv7l-2.7/senseme/__init__.py -> build/bdist.linux-armv7l/egg/senseme
creating build/bdist.linux-armv7l/egg/senseme/lib
copying build/lib.linux-armv7l-2.7/senseme/lib/__init__.py -> build/bdist.linux-armv7l/egg/senseme/lib
copying build/lib.linux-armv7l-2.7/senseme/lib/mwt.py -> build/bdist.linux-armv7l/egg/senseme/lib
copying build/lib.linux-armv7l-2.7/senseme/lib/perpetualtimer.py -> build/bdist.linux-armv7l/egg/senseme/lib
copying build/lib.linux-armv7l-2.7/senseme/lib/background_monitor.py -> build/bdist.linux-armv7l/egg/senseme/lib
copying build/lib.linux-armv7l-2.7/senseme/lib/xml.py -> build/bdist.linux-armv7l/egg/senseme/lib
copying build/lib.linux-armv7l-2.7/senseme/senseme.py -> build/bdist.linux-armv7l/egg/senseme
copying build/lib.linux-armv7l-2.7/senseme/known_attribs.py -> build/bdist.linux-armv7l/egg/senseme
byte-compiling build/bdist.linux-armv7l/egg/bin/__init__.py to __init__.pyc
byte-compiling build/bdist.linux-armv7l/egg/senseme/__init__.py to __init__.pyc
byte-compiling build/bdist.linux-armv7l/egg/senseme/lib/__init__.py to __init__.pyc
byte-compiling build/bdist.linux-armv7l/egg/senseme/lib/mwt.py to mwt.pyc
byte-compiling build/bdist.linux-armv7l/egg/senseme/lib/perpetualtimer.py to perpetualtimer.pyc
byte-compiling build/bdist.linux-armv7l/egg/senseme/lib/background_monitor.py to background_monitor.pyc
byte-compiling build/bdist.linux-armv7l/egg/senseme/lib/xml.py to xml.pyc
byte-compiling build/bdist.linux-armv7l/egg/senseme/senseme.py to senseme.pyc
  File "build/bdist.linux-armv7l/egg/senseme/senseme.py", line 84
    f"SenseMe(name='{self.name}', ip='{self.ip}', "
                                                  ^
SyntaxError: invalid syntax

byte-compiling build/bdist.linux-armv7l/egg/senseme/known_attribs.py to known_attribs.pyc
creating build/bdist.linux-armv7l/egg/EGG-INFO
installing scripts to build/bdist.linux-armv7l/egg/EGG-INFO/scripts
running install_scripts
running build_scripts
creating build/scripts-2.7
copying and adjusting bin/senseme_cli -> build/scripts-2.7
changing mode of build/scripts-2.7/senseme_cli from 644 to 755
creating build/bdist.linux-armv7l/egg/EGG-INFO/scripts
copying build/scripts-2.7/senseme_cli -> build/bdist.linux-armv7l/egg/EGG-INFO/scripts
changing mode of build/bdist.linux-armv7l/egg/EGG-INFO/scripts/senseme_cli to 755
copying SenseMe.egg-info/PKG-INFO -> build/bdist.linux-armv7l/egg/EGG-INFO
copying SenseMe.egg-info/SOURCES.txt -> build/bdist.linux-armv7l/egg/EGG-INFO
copying SenseMe.egg-info/dependency_links.txt -> build/bdist.linux-armv7l/egg/EGG-INFO
copying SenseMe.egg-info/top_level.txt -> build/bdist.linux-armv7l/egg/EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating dist
creating 'dist/SenseMe-0.1.3-py2.7.egg' and adding 'build/bdist.linux-armv7l/egg' to it
removing 'build/bdist.linux-armv7l/egg' (and everything under it)
Processing SenseMe-0.1.3-py2.7.egg
Copying SenseMe-0.1.3-py2.7.egg to /usr/local/lib/python2.7/dist-packages
Adding SenseMe 0.1.3 to easy-install.pth file
Installing senseme_cli script to /usr/local/bin

Installed /usr/local/lib/python2.7/dist-packages/SenseMe-0.1.3-py2.7.egg
Processing dependencies for SenseMe==0.1.3
Finished processing dependencies for SenseMe==0.1.3

cli:

pi@62fib-raspberry:~/SenseMe $ senseme_cli
Traceback (most recent call last):
  File "/usr/local/bin/senseme_cli", line 4, in <module>
    __import__('pkg_resources').run_script('SenseMe==0.1.3', 'senseme_cli')
  File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 534, in run_script
    self.require(requires)[0].run_script(script_name, ns)
  File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 1444, in run_script
    script_code = compile(script_text, script_filename,'exec')
  File "/usr/local/lib/python2.7/dist-packages/SenseMe-0.1.3-py2.7.egg/EGG-INFO/scripts/senseme_cli", line 21
    VALID_PARAMS = f"on off {' '.join([str(x) for x in range(17)])}".split()
                                                                   ^
SyntaxError: invalid syntax

usage:

pi@62fib-raspberry:~/SenseMe $ python test.py
Traceback (most recent call last):
  File "test.py", line 2, in <module>
    from senseme import discover
  File "build/bdist.linux-armv7l/egg/senseme/__init__.py", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/SenseMe-0.1.3-py2.7.egg/senseme/senseme.py", line 84
    f"SenseMe(name='{self.name}', ip='{self.ip}', "
                                                  ^
SyntaxError: invalid syntax
pi@62fib-raspberry:~/SenseMe $ python --version
Python 2.7.9

Is this an arm issue or a known issue with 2.7?

Issues due to not waiting for responses

Given the way this library is communicating with the fan, specifically the receiving messages after sending a command portion, very quickly polling after sending a command can result in receiving the response to the command rather than the response to the poll, and attempting to return that.

Workarounds are being used for the two most likely methods where this would become evident, speed and brightness.

Documented in issues #19, #37.

While it would be slower, the proper way to fix this is probably to wait for the response to the command, and returning based on that rather than fire and forget. Another option would be to verify that the received response to any query is actually a response to that query. This would be faster, but might be more effort.

Both probably need to happen, assuming the former doesn't slow things down much, which I don't think it will.

A fix for this may lead to breaking changes and a major version bump.

Fan returns ON/OFF rather than an integer on Brightness

If quickly polling the brightness immediately after setting it the H Series fan with light seems to frequently return ON/OFF rather than an integer for the brightness level.

This can be demonstrated from a repl by running:

f = discover()[0]
# with light currently on.
# setting brightness = 0 will turn the light off
f.brightness = 0; f.brightness

# or 
# with light currently off.
# setting brightness = 1 will turn the light on
f.brightness = 1; f.brightness
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/tom/dev/SenseMe/senseme/senseme.py", line 140, in brightness
    return int(self._query("<%s;LIGHT;LEVEL;GET;ACTUAL>" % self.name))
ValueError: invalid literal for int() with base 10: 'ON'

Chat

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.

_get_all_request() is not handling two sections in one line correctly.

I am using SenseMe with monitoring enabled and get_attribute(). I have had a lot of problems get_attribute() causing key errors for say 'FAN;SPD;ACTUAL'. Note it doesn't always cause a key error only sometimes. What I think is happening is _get_all_request() is mishandling two sections in one string.

Running following code pprint.pprint(device._get_all_request()) produces the following results:

['Studio Beam Fan;DEVICE;BEEPER;ON',
 'Studio Beam Fan;DEVICE;INDICATORS;ON',
 'Studio Beam Fan;DEVICE;LIGHT;PRESENT',
 'Studio Beam Fan;DEVICE;SERVER;PRODUCTION',
 'Studio Beam Fan;ERRORLOG;ENTRIES;NUM;10',
 'Studio Beam Fan;ERRORLOG;ENTRIES;MAX;10',
 'Studio Beam Fan;FAN;AUTO;OFF',
 'Studio Beam Fan;FAN;DIR;FWD',
 'Studio Beam Fan;FAN;PWR;ON',
 'Studio Beam Fan;FAN;SPD;ACTUAL;5Studio Beam Fan;FAN;SPD;MAX;7',
 'Studio Beam Fan;FAN;SPD;MIN;1',
 'Studio Beam Fan;FAN;BOOKENDS;1;7',
 'Studio Beam Fan;FAN;TIMER;CURR;0',
 'Studio Beam Fan;FAN;TIMER;MAX;7Studio Beam Fan;FAN;TIMER;MIN;1',
 'Studio Beam Fan;FAN;WHOOSH;STATUS;OFF',
 'Studio Beam Fan;FW;NAME;FW000003',
 'Studio Beam Fan;FW;FW000003;2.5.0',
 'Studio Beam Fan;GROUP;LIST;Studio',
 'Studio Beam Fan;GROUP;ROOM;TYPE;4',
 'Studio Beam Fan;LEARN;MAXSPEED;7',
 'Studio Beam Fan;LEARN;MINSPEED;0',
 'Studio Beam Fan;LEARN;ZEROTEMP;2209',
 'Studio Beam Fan;LIGHT;AUTO;OFF',
 'Studio Beam Fan;LIGHT;LEVEL;ACTUAL;0',
 'Studio Beam Fan;LIGHT;BOOKENDS;1;16',
 'Studio Beam Fan;LIGHT;LEVEL;MAX;16',
 'Studio Beam Fan;LIGHT;LEVEL;MIN;1',
 'Studio Beam Fan;LIGHT;PWR;OFF',
 'Studio Beam Fan;NAME;VALUE;Studio Beam Fan',
 'Studio Beam Fan;NW;AP;STATUS;OFF',
 'Studio Beam Fan;NW;DHCP;OFF',
 'Studio Beam Fan;NW;PARAMS;ACTUAL;removed',
 'Studio Beam Fan;NW;SSID;Bogus WiFi,
 'Studio Beam Fan;NW;TOKEN;Removed',
 'Studio Beam Fan;SCHEDULE;CAP;MAX EVENTS,29',
 'Studio Beam Fan;SCHEDULE;EVENT;LIST;NONE',
 'Studio Beam Fan;SLEEP;EVENT;OFF',
 'Studio Beam Fan;SLEEP;EVENT;ON',
 'Studio Beam Fan;SLEEP;STATE;OFF',
 'Studio Beam Fan;SMARTMODE;ACTUAL;COOLING',
 'Studio Beam Fan;SMARTMODE;STATE;FOLLOWTSTAT',
 'Studio Beam Fan;SMARTSLEEP;IDEALTEMP;1926',
 'Studio Beam Fan;SMARTSLEEP;MAXSPEED;7',
 'Studio Beam Fan;SMARTSLEEP;MINSPEED;0',
 'Studio Beam Fan;SNSROCC;TIMEOUT;CURR;600000',
 'Studio Beam Fan;SNSROCC;TIMEOUT;MAX;86400000',
 'Studio Beam Fan;SNSROCC;TIMEOUT;MIN;60000',
 'Studio Beam Fan;TIME;VALUE;2018-06-16T17:23:00Z',
 'Studio Beam Fan;WINTERMODE;HEIGHT;213',
 'Studio Beam Fan;WINTERMODE;STATE;OFF']

I think this shows 'FAN;SPD;ACTUAL' will never end up as a key. Note that the two sections on one line is not consistent so sometimes 'FAN;SPD;ACTUAL' will exist as a key.

Adding .replace(')(', ')||(') to your _get_all_request() fixes the problem for me.

def _get_all_request(self):
    """Get all parameters from device, returns as a list."""
    results = self.send_raw('<%s;GETALL>' % self.name)
    # sometimes this gets two sections in one string:
    # join list to str, clean up (), and split back to a list
    results = '||'.join(results).replace(')(', ')||(')
    return results.replace('(', '').replace(')', '').split('||')

Now pprint.pprint(device._get_all_request()) produces:

['Studio Beam Fan;DEVICE;BEEPER;ON',
 'Studio Beam Fan;DEVICE;INDICATORS;ON',
 'Studio Beam Fan;DEVICE;LIGHT;PRESENT',
 'Studio Beam Fan;DEVICE;SERVER;PRODUCTION',
 'Studio Beam Fan;ERRORLOG;ENTRIES;NUM;10',
 'Studio Beam Fan;ERRORLOG;ENTRIES;MAX;10',
 'Studio Beam Fan;FAN;AUTO;OFF',
 'Studio Beam Fan;FAN;DIR;FWD',
 'Studio Beam Fan;FAN;PWR;ON',
 'Studio Beam Fan;FAN;SPD;ACTUAL;5',
 'Studio Beam Fan;FAN;SPD;MAX;7',
 'Studio Beam Fan;FAN;SPD;MIN;1',
 'Studio Beam Fan;FAN;BOOKENDS;1;7',
 'Studio Beam Fan;FAN;TIMER;CURR;0',
 'Studio Beam Fan;FAN;TIMER;MAX;7',
 'Studio Beam Fan;FAN;TIMER;MIN;1',
 'Studio Beam Fan;FAN;WHOOSH;STATUS;OFF',
 'Studio Beam Fan;FW;NAME;FW000003',
 'Studio Beam Fan;FW;FW000003;2.5.0',
 'Studio Beam Fan;GROUP;LIST;Studio',
 'Studio Beam Fan;GROUP;ROOM;TYPE;4',
 'Studio Beam Fan;LEARN;MAXSPEED;7',
 'Studio Beam Fan;LEARN;MINSPEED;0',
 'Studio Beam Fan;LEARN;ZEROTEMP;2209',
 'Studio Beam Fan;LIGHT;AUTO;OFF',
 'Studio Beam Fan;LIGHT;LEVEL;ACTUAL;0',
 'Studio Beam Fan;LIGHT;BOOKENDS;1;16',
 'Studio Beam Fan;LIGHT;LEVEL;MAX;16',
 'Studio Beam Fan;LIGHT;LEVEL;MIN;1',
 'Studio Beam Fan;LIGHT;PWR;OFF',
 'Studio Beam Fan;NAME;VALUE;Studio Beam Fan',
 'Studio Beam Fan;NW;AP;STATUS;OFF',
 'Studio Beam Fan;NW;DHCP;OFF',
 'Studio Beam Fan;NW;PARAMS;ACTUAL;removed',
 'Studio Beam Fan;NW;SSID;Bogus WiFi',
 'Studio Beam Fan;NW;TOKEN;removed',
 'Studio Beam Fan;SCHEDULE;CAP;MAX EVENTS,29',
 'Studio Beam Fan;SCHEDULE;EVENT;LIST;NONE',
 'Studio Beam Fan;SLEEP;EVENT;OFF',
 'Studio Beam Fan;SLEEP;EVENT;ON',
 'Studio Beam Fan;SLEEP;STATE;OFF',
 'Studio Beam Fan;SMARTMODE;ACTUAL;COOLING',
 'Studio Beam Fan;SMARTMODE;STATE;FOLLOWTSTAT',
 'Studio Beam Fan;SMARTSLEEP;IDEALTEMP;1926',
 'Studio Beam Fan;SMARTSLEEP;MAXSPEED;7',
 'Studio Beam Fan;SMARTSLEEP;MINSPEED;0',
 'Studio Beam Fan;SNSROCC;TIMEOUT;CURR;600000',
 'Studio Beam Fan;SNSROCC;TIMEOUT;MAX;86400000',
 'Studio Beam Fan;SNSROCC;TIMEOUT;MIN;60000',
 'Studio Beam Fan;TIME;VALUE;2018-06-16T17:54:39Z',
 'Studio Beam Fan;WINTERMODE;HEIGHT;213',
 'Studio Beam Fan;WINTERMODE;STATE;OFF']

Discovery doesn't work on a Chromebook

It seems this is due to Chromebook's firewalling off incoming UDP, including under Crouton. A Chromebook would probably need a proxy for discovery, or to hardcode device IPs.

I suppose a config file of discovered devices would also work.

Trouble Discovering Fans

I opened up a UDP connection for in and outbound traffic in my firewall on port 31415, but I don't get back anything when running discover() and when I run SenseMe(name="Office"), it never returns anything. I am running windows.

I am assuming this is a problem with my firewall due to lack of understanding with how the fan works, does somebody know what I should do to fix this?

discover_single_device doesn't use known details

As it is right now, discover_single_device doesn't use any known details that were given during instantiation, rather it discovers the first device that comes to it.

See comments on issue #1 for how to fix this.

Update cache when properties are changed

When monitoring is enabled the cache is used to speed up status requests via get_attribute(). However when a property (i.e. brightness) is changed the cache is not updated until the next status update from the fan which could be 45 seconds later.

An example of why this is needed is Home Assistant automation software. It separates the command path from the status path. So when I turn on the fan in Home Assistant it takes upwards of 45 seconds before the on state of the fan is accurately reflected in Home Assistant.

I propose that the cache be updated along with any successful property change. Below is an example of changes to your brightness setter property:

    @brightness.setter
    def brightness(self, light):
        if light > 16:
            light = 16
        elif light < 0:
            light = 0
        self._send_command('<%s;LIGHT;LEVEL;SET;%s>' % (self.name, light))
        # put new brightness in cache if monitoring
        if self._monitoring and self._all_cache:
            self._all_cache['LIGHT;LEVEL;ACTUAL'] = str(light)
            # changes to brightness also changes light power
            if light > 0:
                self._all_cache['LIGHT;PWR'] = 'ON'
            else:
                self._all_cache['LIGHT;PWR'] = 'OFF'

I have made these changes already. Would like me to issue a Pull Request? I have never done a PR and may need some help.

_query method has odd returns

_query method returns an output or false, this is likely better handled by an exception

The query method is used in many functions, would add a lot of exception handling. Perhaps return a boolean and the value / empty string

Consider fan, light, light+fan classes

Is there an advantage to that?

As far as I know a fan without a light kit shouldn't have a problem with irrelevant commands sent to it.

But, what happens when a light query is sent to a fan without a light kit, or a light without a fan?

Potential advantages:
Easier to add other IoT devices if BAF/Haiku makes another IoT device that is neither a fan or a light. (Garage door opener?) Or, perhaps a garage door opener with a light. This would make for fewer methods to have to look through when controlling a device. (Does that matter?)

Potential downsides:
Discovery will end up returning a list of objects that have different methods and user will have to avoid calling a method that isn't appropriate for the device type.

Could alleviate this by allowing for discovery of only fans, only lights, only fans with light kits, only garage door openers, etc.

Complicates things


Thoughts on this are a SenseMe class and Fan, Light, Toaster would inherit from SenseMe. Then a combination device FanWithLight would inherit from Fan and Light.

Whoosh property does not work.

The whoosh property setter command sent to the fan is malformed and does not work.

The whoosh get property returns a string containing either 'ON' or 'OFF' when it should return True/False like other ON/OFF properties.

Flask example: expand to support all library methods

I think it is most of the way there. (Missing whoosh for sure.) This would make for an easy exposure to Flask for a beginner.

If this is done and it supported multiple devices (#18 (comment)) it could move to a script in setup. It is hard coded to the first discovered device at present.

setup.py and distribution

Since the methods seem to be closer to stable in name and function at this point, get setup.py posted.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.