Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

asyncio and non-blocking UDP, rhythm support, performance optimizations, refactoring #2

Merged
merged 1 commit into from
Mar 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 85 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,84 +1,116 @@
# pywizlight
A python connector for WiZ light bulbs.
Work in progress an only tested with the [SLV Play RGB bulb](https://www.amazon.de/dp/B07PNCDJLW).

Tested with the following lights:
[Original Phillips Wiz WiFi LEDs](https://www.lighting.philips.co.in/consumer/smart-wifi-led)
[SLV Play RGB bulb](https://www.amazon.de/dp/B07PNCDJLW)

## Example
```python
from pywizlight import wizlight
from pywizlight.bulb import wizlight, PilotBuilder
# create/get the current thread's asyncio loop
loop = asyncio.get_event_loop()
# setup a standard light
light = wizlight("<your bulb ip")
# setup the light with a custom port
light = wizlight("<your bulb ip",12345)
# turn on the light
light.turn_on()
# set bulb to warm white color
light.warm_white = 255
# print the value
print(light.warm_white)
# set red to 255 = 100%
light.rgb = 255, 0, 0
# set green to 255 = 100%
light.rgb = 0, 255, 0
# set blue to 255 = 100%
light.rgb = 0, 0, 255
# get the current values
r, g, b = light.rgb

#the following calls need to be done inside an asyncio coroutine
#to run them fron normal synchronous code, you can wrap them with asyncio.run(..)
#see test.py for examples

# turn on the light into "rhythm mode"
await light.turn_on(PilotBuilder())
# set bulb brightness
await light.turn_on(PilotBuilder(brightness = 255)

# set bulb brightness (with async timeout)
timeout_secs=10
await asyncio.wait_for(light.turn_on(PilotBuilder(brightness = 255)), wait_secs)

# set bulb to warm white
await light.turn_on(PilotBuilder(warm_white = 255)

# set rbb values
# red to 0 = 0%, green to 128 = 50%, blue to 255 = 100%
await light.turn_on(PilotBuilder(rgb = (0, 128, 255))

# get the current color temperature, rgb values
state = await light.updateState()
print(state.get_colortemp())
r, g, b = state.get_rgb()
print("red %i green %i blue %i" % (r, g, b))

# start a scene
light.scene = 14 # party
await light.turn_on(PilotBuilder(scene = 14)) # party

# get the name of the current scene
print(light.scene)
# set brightness to 255 = 100%
light.brightness = 255
state = await light.updateState()
print(state.get_scene())

# turns the light off
light.turn_off()
await light.turn_off()

# do operations on multiple lights parallely
bulb1 = wizlight("<your bulb1 ip>")
bulb2 = wizlight("<your bulb2 ip>")
await asyncio.gather(bulb1.turn_on(PilotBuilder(brightness = 255),
bulb2.turn_on(PilotBuilder(warm_white = 255), loop = loop)

```

## Bulb paramters (UDP RAW):
- **sceneId** - calls one of thr predefined scenes (int from 0 to 32) [Wiki](https://github.com/sbidy/pywizlight/wiki/Light-Scenes)
- **speed** - sets the color changing speed in precent
- **dimming** - sets the dimmer of the bulb in precent
- **temp** - sets color temperature in kelvins
- **r** - red color range 0-255
- **g** - green color range 0-255
- **b** - blue color range 0-255
- **c** - cold white range 0-255
- **w** - warm white range 0-255
- **id** - the bulb id
- **state** - when it's on or off
- **schdPsetId** - rhythm id of the room

## Async I/O
For async I/O this component uses https://github.com/jsbronder/asyncio-dgram, which internally uses asyncio DatagramTransport, which allows completely non-blocking UDP transport

## Classes

`wizlight(ip)` Creates a instance of a WiZ Light Bulb. Constructor with ip of the bulb

### Instance variables
`brightness`gets the value of the brightness 0-255

`color` get the rgbW color state of the bulb and turns it on
You need to first fetch the state by calling `light.updateState()`
After that all state can be fetched from `light.state`, which is a `PilotParser` object

`colortemp` get the color temperature ot the bulb
`PilotParser.get_brightness()`gets the value of the brightness 0-255

`rgb` get the rgb color state of the bulb and turns it on
`PilotParser.get_rgb()` get the rgbW color state of the bulb

`status` returns true or false / true = on , false = off
`PilotPerson.get_colortemp()` get the color temperature ot the bulb

### Methods
`getBulbConfig(self)` returns the configuration from the bulb
`PilotPerson.get_warm_white/get_cold_white()` get the current warm/cold setting (not supported by original Phillips Wiz bulbs)

`PilotPerson.get_scene()` gets the current scene name

`getState(self)` gets the current bulb state - no paramters need to be included
`PilotPerson.get_state()` returns true or false / true = on , false = off

`hex_to_percent(self, hex)` helper for convertring 0-255 to 0-100
### Methods
`getBulbConfig(self)` returns the hardware configuration of the bulb

`percent_to_hex(self, percent)` helper for converting 0-100 into 0-255
`updateState(self)` gets the current bulb state from the light using `sendUDPMessage` and sets it to `self.state`

`lightSwitch(self)` turns the light bulb on or off like a switch

`sendUDPMessage(self, message)` send the udp message to the bulb
`sendUDPMessage(self, message, timeout = 60, send_interval = 0.5, max_send_datagrams = 100):` sends the udp message to the bulb. Since UDP can loose packets, and your light might be a long distance away from the router, we continuously keep sending the UDP command datagram until there is a response from the light. This has in tests worked way better than just sending once and just waiting for a timeout. You can set the async operation timeout using `timeout`, the time interval to sleep between continuous UDP sends using `send_interval` and the maximum number of continuous pings to send using `max_send_datagrams`. It is already hard coded to a lower value for `setPilot` (set light state) vs `getPilot` (fetch light state) so as to avoid flickering the light.

`turn_off(self)` turns the light off

`turn_on(self)` turns the light on
`turn_on(PilotBuilder)` turns the light on. This take a `PilotBuilder` object, which can be used to set all the parameters programmtically - rgb, color temperature, brightness, etc. To set the light to rhythm mode, create an empty `PilotBuilder`.

## Bulb methodes (UDP nativ):
## Bulb methodes (UDP native):
- **getSystemConfig** - gets the current system configuration - no paramters need
- **syncPilot** - sent by the bulb as heart-beats
- **getPilot** - gets the current bulb state - no paramters need to be included
Expand All @@ -87,11 +119,28 @@ Work in progress an only tested with the [SLV Play RGB bulb](https://www.amazon.
- **Registration** - used to "register" with the bulb: This notifies the built that
it you want it to send you heartbeat sync packets.

## Example requests
## Example UDP requests
Send message to the bulb:
`{"method":"setPilot","params":{"r":255,"g":255,"b":255,"dimming":50}}`
Response: `{"method":"setPilot","env":"pro","result":{"success":true}}`

Get state of the bulb:
`{"method":"getPilot","params":{}}`
Response: `{"method":"getPilot","env":"pro","result":{"mac":"0000000000","rssi":-65,"src":"","state":false,"sceneId":0,"temp":6500,"dimming":100}}`
Responses:

custom color mode:

`{'method': 'getPilot', 'env': 'pro', 'result': {'mac': 'a8bb50a4f94d', 'rssi': -60, 'src': '', 'state': True, 'sceneId': 0, 'temp': 5075, 'dimming': 47}}`

scene mode:

`{'method': 'getPilot', 'env': 'pro', 'result': {'mac': 'a8bb50a4f94d', 'rssi': -65, 'src': '', 'state': True, 'sceneId': 12, 'speed': 100, 'temp': 4200, 'dimming': 47}}`

rhythm mode:

`{'method': 'getPilot', 'env': 'pro', 'result': {'mac': 'a8bb50a4f94d', 'rssi': -63, 'src': '', 'state': True, 'sceneId': 14, 'speed': 100, 'dimming': 100, 'schdPsetId': 9}}`

## Contributors

@sbidy for the entire python library from scratch with complete light control
@angadsingh for implementing asyncio and non-blocking UDP, rhythm support, performance optimizations (https://github.com/angadsingh/wiz_light/commit/caa90cebd9f8ccb2d588c900e36fbf19277eda9c)
Loading