#!/usr/bin/python3 # # Interact with Shelly brand devices. # import sys import asyncio import logging import functools import aiohttp import easydict import webcolors # pip3 install webcolors import yaml # If invoked as a script, don't worry about APP APP = getattr(sys.modules['__main__'], 'APP', None) LOGGER = logging.getLogger(__name__) DEFAULT_BRIGHTNESS = 10 class Device: # RGB strips via Shelly controllers ### maybe 24VDC can lights via same type controllers def __init__(self, ip): self.ip = ip async def invoke(self, rpc, **params): url = f'http://{self.ip}/rpc/{rpc}' async with APP.session.get(url, params=params) as response: LOGGER.debug(f'STATUS: {response.status} {response.reason}') if response.status != 200: LOGGER.info('ERROR BODY: ' + await response.text()) return None return easydict.EasyDict(await response.json()) @staticmethod def api(rpc): "Return a decorator for RPC." def wrapper(func): "Wrap func with an HTTP invocation of RPC." @functools.wraps(func) async def call_endpoint(self, *args, **kw): LOGGER.debug(f'CALLING: {rpc}, *{args}, **{kw}') params = func(self, *args, **kw) return await self.invoke(rpc, **params) return call_endpoint return wrapper @api('Shelly.GetStatus') def status(self, id=0): return { 'id': id, } @api('RGBW.Set') def turn_on(self, id=0, color='white', bright=DEFAULT_BRIGHTNESS): r, g, b = webcolors.html5_parse_legacy_color(color) return { 'id': id, 'on': 'true', 'brightness': bright, 'rgb': f'[{r},{g},{b}]', } @api('RGBW.Set') def turn_off(self, id=0): return { 'id': id, 'on': 'false', } async def configure(cfg): print('CONFIGURE:', cfg.lights) async def random_tests(cfg): import pprint d = Device('192.168.0.17') print(await d.status()) async def print_result(m, **kw): result = await d.invoke(m, **kw) pprint.pprint(result) await print_result('Shelly.GetDeviceInfo') #await print_result('Sys.GetStatus') #await print_result('Shelly.ListMethods') c = await d.invoke('Shelly.GetComponents') pprint.pprint([comp.key for comp in c.components]) await d.invoke('RGBW.Set?id=0&on=true&brightness=100&rgb=[0,255,0]') async def run_test(): import argparse logging.basicConfig(level=logging.DEBUG) parser = argparse.ArgumentParser(description='Shelly device manager') parser.add_argument('--configure', action='store_true', help='(re)configure all Shelly devices') args = parser.parse_args() cfg = easydict.EasyDict(yaml.safe_load(open('config.yaml'))) try: # Create an "APP" with a session object. global APP APP = easydict.EasyDict(session=aiohttp.ClientSession()) if args.configure: await configure(cfg) else: await random_tests(cfg) finally: await APP.session.close() if __name__ == '__main__': asyncio.run(run_test())