Metadata-Version: 2.1
Name: pywizlight
Version: 0.4.2
Summary: A python connector for WiZ light bulbs (e.g SLV Play)
Home-page: https://github.com/sbidy/pywizlight
Author: Stephan Traub
Author-email: sbidy@hotmail.com
License: MIT
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: ~=3.7
Description-Content-Type: text/markdown
Requires-Dist: asyncio-dgram
Requires-Dist: click

![Upload Python Package](https://github.com/sbidy/pywizlight/workflows/Upload%20Python%20Package/badge.svg)
![Code Quality Check](https://github.com/sbidy/pywizlight/workflows/Lint/badge.svg)
![Update Docs](https://github.com/sbidy/pywizlight/workflows/Update%20Docs/badge.svg)

# pywizlight

A Python connector for WiZ light bulbs.

Tested with the following smart 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)
- [WiZ Smart LED](https://www.amazon.de/gp/product/B07YLW52MK)

## Install

```bash
pip install pywizlight
```

**Note:** Requires Python version `>=3.7`.

On a Fedora-based system or on a CentOS/RHEL 8 machine which has EPEL enabled, as
[`pywizlight`](https://src.fedoraproject.org/rpms/python-pywizlight) is present in the
Fedora Package Collection.

```bash
sudo dnf -y install python3-pywizlight
```

## Kudos

Thank you [@angadsingh](https://github.com/angadsingh) for make such incredible improvements!!

Thanks to [@fabaff](https://github.com/fabaff) for the CLI tool and some code rework!

## Example

```python
import asyncio

from pywizlight import wizlight, PilotBuilder, discovery

async def main():
    """Sample code to work with bulbs."""
    # Discover all bulbs in the network via broadcast datagram (UDP)
    # function takes the discovery object and returns a list with wizlight objects.
    bulbs = await discovery.find_wizlights(discovery)
    # Print the IP address of the bulb on index 0
    print(f"Bulb IP address: {bulbs[0].ip}")

    # Iterate over all returned bulbs
    for bulb in bulbs:
        print(bulb.__dict__)
        # Turn off all available bulbs
        # await bulb.turn_off()

    # Set up a standard light
    light = wizlight("192.168.0.170")
    # Set up the light with a custom port
    #light = wizlight("your bulb's IP address", port=12345)

    # 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(..).

    # 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 = 10
    await asyncio.wait_for(light.turn_on(PilotBuilder(brightness = 255)), timeout)

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

    # Set RGB 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())
    red, green, blue = state.get_rgb()
    print(f"red {red}, green {green}, blue {blue}")

    # Start a scene
    await light.turn_on(PilotBuilder(scene = 4)) # party

    # Get the name of the current scene
    state = await light.updateState()
    print(state.get_scene())

    # Get the features of the bulb
    bulb_type = await bulb.get_bulbtype()
    print(bulb_type.features.brightness) # returns true if brightness is supported
    print(bulb_type.features.color) # returns true if color is supported
    print(bulb_type.features.color_tmp) # returns true if color temperatures are supported
    print(bulb_type.features.effect) # returns true if effects are supported
    print(bulb_type.kelvin_range.max) # returns max kelvin in in INT
    print(bulb_type.kelvin_range.min) # returns min kelvin in in INT
    print(bulb_type.name) # returns the module name of the bulb

    # Turns the light 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)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
```

## CLI

`wizlight` is a command-line tool to perform basic interactions with bulbs.

```bash
$ wizlight
Usage: wizlight [OPTIONS] COMMAND [ARGS]...

  Simple command-line tool to interact with Wizlight bulbs.

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Commands:
  discover  Disocver bulb in the local network.
  off       Turn the bulb off.
  on        Turn the bulb on.
  state     Get the current state from the given bulb.
```

## Discovery

The discovery works with an UDP Broadcast request and collects all bulbs in the network.

## Bulb paramters (UDP RAW)

- **sceneId** - calls one of thr predefined scenes (int from 0 to 32) [List of names in code](https://github.com/sbidy/pywizlight/blob/master/pywizlight/scenes.py)
- **speed** - sets the color changing speed in percent
- **dimming** - sets the dimmer of the bulb in percent
- **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. Constructed with the IP address of the bulb.

### Instance variables

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.

`PilotParser.get_brightness()`gets the value of the brightness 0-255

`PilotParser.get_rgb()` get the rgbW color state of the bulb

`PilotParser.get_colortemp()` get the color temperature ot the bulb

`PilotParser.get_warm_white/get_cold_white()` get the current warm/cold setting (not supported by original Phillips Wiz bulbs)

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

`PilotParser.get_state()` returns true or false / true = on , false = off

### Methods

`getBulbConfig(self)` returns the hardware configuration of the bulb

`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

`getMAC(self)` returns the MAC address of the bulb. Can be used as unique ID.

`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(PilotBuilder)` turns the light on. This take a `PilotBuilder` object, which can be used to set all the parameters programmatically - rgb, color temperature, brightness, etc. To set the light to rhythm mode, create an empty `PilotBuilder`.

## Bulb methods (UDP native):

- **getSystemConfig** - gets the current system configuration - no parameters need
- **syncPilot** - sent by the bulb as heart-beats
- **getPilot** - gets the current bulb state - no parameters need to be included
- **setPilot** - used to tell the bulb to change color/temp/state
- **Pulse** - uncertain of purpose
- **Registration** - used to "register" with the bulb: This notifies the built that
  it you want it to send you heartbeat sync packets.

## 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":{}}`
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](http://github.com/sbidy) for the entire python library from scratch with complete light control
- [@angadsingh](http://github.com/angadsingh) for implementing asyncio and non-blocking UDP, rhythm support, performance optimizations


