First commit
This commit is contained in:
commit
0a732b478a
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Source for the following rules: https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
|
||||||
|
*.egg-info/
|
4
pyproject.toml
Normal file
4
pyproject.toml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
15
setup.cfg
Normal file
15
setup.cfg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[metadata]
|
||||||
|
name = citadel.devices
|
||||||
|
version = 0.0.1
|
||||||
|
|
||||||
|
[options]
|
||||||
|
package_dir =
|
||||||
|
=src
|
||||||
|
packages = find_namespace:
|
||||||
|
install_requires =
|
||||||
|
typer==0.3.2
|
||||||
|
citadel.mqtt==0.0.1
|
||||||
|
|
||||||
|
[options.packages.find]
|
||||||
|
where = src
|
||||||
|
|
103
src/citadel/devices/__init__.py
Normal file
103
src/citadel/devices/__init__.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
from citadel.mqtt import Client
|
||||||
|
|
||||||
|
class Light:
|
||||||
|
|
||||||
|
def __init__(self, _id: str, client: Client, **kwargs):
|
||||||
|
self._state = None
|
||||||
|
self._change_callback = None
|
||||||
|
|
||||||
|
self._id = _id
|
||||||
|
|
||||||
|
self.STATE_TOPIC = kwargs.get('STATE_TOPIC', 'stat/{id}/POWER1')
|
||||||
|
self.STATE_ON_PAYLOAD = kwargs.get('STATE_ON_PAYLOAD', 'ON')
|
||||||
|
self.STATE_OFF_PAYLOAD = kwargs.get('STATE_OFF_PAYLOAD', 'OFF')
|
||||||
|
|
||||||
|
self.CMND_TOPIC = kwargs.get('CMND_TOPIC', 'cmnd/{id}/POWER1')
|
||||||
|
self.CMND_ON_PAYLOAD = kwargs.get('CMND_ON_PAYLOAD', 'ON')
|
||||||
|
self.CMND_OFF_PAYLOAD = kwargs.get('CMND_OFF_PAYLOAD', 'OFF')
|
||||||
|
|
||||||
|
self._client = client
|
||||||
|
self._client.subscribe_callback(self.STATE_TOPIC.format(id=self.id), self.onStateChange)
|
||||||
|
|
||||||
|
def setChangeCallback(self, callback: typing.Callable):
|
||||||
|
self._change_callback = callback
|
||||||
|
|
||||||
|
def onStateChange(self, client, userdata, msg):
|
||||||
|
state = msg.payload.decode()
|
||||||
|
|
||||||
|
if state == self.STATE_ON_PAYLOAD:
|
||||||
|
self._state = True
|
||||||
|
elif state == self.STATE_OFF_PAYLOAD:
|
||||||
|
self._state = False
|
||||||
|
else:
|
||||||
|
raise Exception("Unexpected value %s for stat topic" % state)
|
||||||
|
|
||||||
|
if self._change_callback:
|
||||||
|
self._change_callback(self)
|
||||||
|
|
||||||
|
def on(self):
|
||||||
|
self._client.publish(self.CMND_TOPIC.format(id=self.id), self.CMND_ON_PAYLOAD)
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
self._client.publish(self.CMND_TOPIC.format(id=self.id), self.CMND_OFF_PAYLOAD)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
|
||||||
|
class LightsGroup:
|
||||||
|
|
||||||
|
def __init__(self, _id: str, client: Client, lights: [Light]):
|
||||||
|
""" Create a virtual mqtt device that handle multiple light """
|
||||||
|
self._id = _id
|
||||||
|
self._lights = lights
|
||||||
|
|
||||||
|
for l in self._lights:
|
||||||
|
l.setChangeCallback(self.onLightStateChange)
|
||||||
|
|
||||||
|
self._client = client
|
||||||
|
self._client.subscribe_callback('cmnd/%s/POWER' % self._id, self.cmndPower)
|
||||||
|
|
||||||
|
self._previous_state = all([l.state for l in self._lights])
|
||||||
|
|
||||||
|
def cmndPower(self, client, userdata, msg):
|
||||||
|
value = msg.payload.decode()
|
||||||
|
if value == 'ON':
|
||||||
|
self.on()
|
||||||
|
elif value == 'OFF':
|
||||||
|
self.off()
|
||||||
|
elif value == '':
|
||||||
|
self._client.publish('stat/%s/POWER' % self.id, 'ON' if self.state else 'OFF' )
|
||||||
|
else:
|
||||||
|
raise Exception("Unexpected value %s for stat topic" % value)
|
||||||
|
|
||||||
|
def onLightStateChange(self, light):
|
||||||
|
state = all([l.state for l in self._lights])
|
||||||
|
if state != self._previous_state:
|
||||||
|
self._client.publish('stat/%s/POWER' % self.id, 'ON' if state else 'OFF')
|
||||||
|
self._previous_state = state
|
||||||
|
|
||||||
|
def on(self):
|
||||||
|
for l in self._lights:
|
||||||
|
l.on()
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
for l in self._lights:
|
||||||
|
l.off()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self._previous_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
|
52
src/citadel/devices/__main__.py
Normal file
52
src/citadel/devices/__main__.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import typer
|
||||||
|
|
||||||
|
from citadel.mqtt import Client
|
||||||
|
|
||||||
|
from . import Light, LightsGroup
|
||||||
|
|
||||||
|
def main(\
|
||||||
|
mqtt_user: str = typer.Option(... , envvar="DEVICES_MQTT_USER"),\
|
||||||
|
mqtt_pwd: str = typer.Option(... , envvar="DEVICES_MQTT_PWD"),\
|
||||||
|
mqtt_host: str = typer.Option(... , envvar="DEVICES_MQTT_HOST"),\
|
||||||
|
mqtt_port: int = typer.Option(... , envvar="DEVICES_MQTT_PORT")\
|
||||||
|
):
|
||||||
|
|
||||||
|
client = Client()
|
||||||
|
client.setup(mqtt_host, mqtt_port, mqtt_user, mqtt_pwd)
|
||||||
|
|
||||||
|
lights1 = LightsGroup('KitchenLed',
|
||||||
|
client, [Light('KitchenCeiling1', client),
|
||||||
|
Light('KitchenCeiling2', client)])
|
||||||
|
|
||||||
|
lights2 = LightsGroup('KitchenCelling',
|
||||||
|
client, [
|
||||||
|
Light('KitchenCeiling1', client,
|
||||||
|
STATE_TOPIC='stat/{id}/POWER2',
|
||||||
|
CMND_TOPIC='cmnd/{id}/POWER2'),
|
||||||
|
Light('KitchenCeiling2', client,
|
||||||
|
STATE_TOPIC='stat/{id}/POWER2',
|
||||||
|
CMND_TOPIC='cmnd/{id}/POWER2')])
|
||||||
|
|
||||||
|
client.loop_forever()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# create logger
|
||||||
|
logger = logging.getLogger('')
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# create console handler and set level to debug
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
ch.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# create formatter
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
# add formatter to ch
|
||||||
|
ch.setFormatter(formatter)
|
||||||
|
|
||||||
|
# add ch to logger
|
||||||
|
logger.addHandler(ch)
|
||||||
|
|
||||||
|
typer.run(main)
|
Loading…
x
Reference in New Issue
Block a user