Skip to content

Commit 52a4dfc

Browse files
authored
!feat: Implement logging (#31)
Moved some items around in CLI and changed how context is passed, hence the breaking change flag
2 parents 68d28d0 + e6de016 commit 52a4dfc

File tree

7 files changed

+73
-49
lines changed

7 files changed

+73
-49
lines changed

src/kc3zvd/iot_state/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# SPDX-FileCopyrightText: 2024-present KC3ZVD <[email protected]>
22
#
3-
# SPDX-License-Identifier: MIT
3+
# SPDX-License-Identifier: MIT

src/kc3zvd/iot_state/cli/__init__.py

+17-19
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,38 @@
44
import click
55
from kc3zvd.iot_state.__about__ import __version__
66
from kc3zvd.iot_state.publishers import mqtt
7+
from .core.mqtt import mqtt
8+
import logging
9+
import sys
10+
11+
12+
logger = logging.getLogger('kc3zvd.iot_state')
13+
handler = logging.StreamHandler(sys.stdout)
14+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
15+
handler.setFormatter(formatter)
16+
logger.addHandler(handler)
717

818
@click.group(context_settings={"help_option_names": ["-h", "--help"], "auto_envvar_prefix": "IOT"}, invoke_without_command=False)
919
@click.version_option(version=__version__, prog_name="iot-state")
20+
@click.option('--log-level', help="Log level", default="INFO", type=str)
1021
@click.option('--redis-username', help="Redis instance username", default='', type=str)
1122
@click.option('--redis-password', help="Redis instance password", default='', type=str)
1223
@click.option('--redis-host', help="Redis instance host", default='localhost', type=str)
1324
@click.option('--redis-port', help="Redis instance port", default=6379, type=int)
1425
@click.option('--redis-db', help="Redis instance DB number", default=0, type=int)
1526
@click.pass_context
16-
def iot_state(ctx, redis_username, redis_password, redis_host, redis_port, redis_db):
27+
def iot_state(ctx, log_level, redis_username, redis_password, redis_host, redis_port, redis_db):
28+
logger.setLevel(level=log_level)
29+
handler.setLevel(level=log_level)
30+
1731
ctx.ensure_object(dict)
1832

1933
if redis_username or redis_password:
2034
if not redis_username or not redis_password:
21-
click.echo("Provide both username and password for redis")
35+
logger.error("Provide both username and password for redis")
2236
exit()
2337
ctx.obj['redis_url'] = f"redis://{redis_username}:{redis_password}@{redis_host}:{redis_port}/{redis_db}"
2438
else:
2539
ctx.obj['redis_url'] = f"redis://{redis_host}:{redis_port}/{redis_db}"
26-
click.echo("Starting IOT state platform")
2740

28-
@iot_state.command()
29-
@click.option('--platform', help="The platform to publish to", required=True,
30-
type=click.Choice(['mqtt'], case_sensitive=False))
31-
@click.option('--mqtt-host', help="The MQTT host to connect to", default='localhost', type=str)
32-
@click.option('--mqtt-port', help="The port to use to connect to the MQTT host", default=1883, type=int)
33-
@click.option('--mqtt-prefix', help="The prefix to use for the MQTT topic", default='', type=str)
34-
@click.pass_context
35-
def publisher(ctx, platform, mqtt_host, mqtt_port, mqtt_prefix):
36-
match platform:
37-
case 'mqtt':
38-
click.echo("mqtt platform selected")
39-
mqtt.run(
40-
redis_url=ctx.obj['redis_url'],
41-
)
42-
case _:
43-
exit()
41+
iot_state.add_command(mqtt)

src/kc3zvd/iot_state/cli/core/mqtt.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from kc3zvd.iot_state.publishers import mqtt as p
2+
import click
3+
4+
@click.group()
5+
@click.option('--host', help="The MQTT host to connect to", default='localhost', type=str)
6+
@click.option('--port', help="The port to use to connect to the MQTT host", default=1883, type=int)
7+
@click.option('--topic_prefix', help="The prefix to use for the MQTT topic", default='', type=str)
8+
@click.pass_context
9+
def mqtt(ctx, host, port, topic_prefix):
10+
ctx.ensure_object(dict)
11+
try:
12+
if topic_prefix[-1] != '/':
13+
topic_prefix = f"{topic_prefix}/"
14+
except IndexError:
15+
pass
16+
17+
18+
ctx.obj['mqtt'] = {
19+
"host": host,
20+
"port": port,
21+
"topic_prefix": topic_prefix
22+
}
23+
24+
@mqtt.command()
25+
26+
@click.pass_context
27+
def publisher(ctx):
28+
p.run()

src/kc3zvd/iot_state/publishers/mqtt.py

+18-21
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,54 @@
44
import asyncio
55
import json
66
import click
7+
import logging
8+
logger = logging.getLogger(__name__)
79
# prefix/device_type/area_name/device_name/state_class
810

911
async def handle_state_messages(channel: redis.client.PubSub):
1012

11-
c = click.get_current_context()
12-
print(c.params)
13-
13+
ctx = click.get_current_context()
1414
mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
15-
mqttc.connect(host=c.params['mqtt_host'], port=c.params['mqtt_port'])
15+
mqttc.connect(host=ctx.obj['mqtt']['host'], port=ctx.obj['mqtt']['port'])
1616
mqttc.loop_start()
1717
while True:
1818
message = await channel.get_message(ignore_subscribe_messages=True)
1919
if message is not None:
20-
print(f"(Reader) Message Received: {message}")
20+
logger.info("Handling state message")
21+
logger.debug(message)
2122

2223
payload = json.loads(message['data'].decode('utf-8'))
2324

2425
try:
2526
device = payload['data']['device']
2627
state = payload['data']['state']
2728
except KeyError:
28-
print("Missing key in message")
29+
logger.infq:("Missing key in message")
2930
continue
3031

3132

32-
print(f"area: {device['area']}")
33-
34-
if c.params['mqtt_prefix']:
35-
if c.params['mqtt_prefix'][-1] != '/':
36-
mqtt_prefix = f"{c.params['mqtt_prefix']}/"
37-
38-
topic = f"{c.params['mqtt_prefix']}{device['device_type']}/{device['area_name']}/{device['friendly_name']}/{state['state_class']}"
33+
topic = f"{ctx.obj['mqtt']['topic_prefix']}{device['device_type']}/{device['area_name']}/{device['friendly_name']}/{state['state_class']}"
3934

4035
message = {
4136
"device": device,
4237
"state": state
4338
}
4439

4540
message = json.dumps(device)
46-
print(f"Sending message to topic {topic}: {message}")
41+
logger.info("Sending message to mqtt")
42+
logger.debug(f"Topic: {topic}")
43+
logger.debug(f"Message: {message}")
4744
mqttc.publish(topic=topic, payload=json.dumps(message)).wait_for_publish()
4845
mqttc.disconnect()
4946
mqttc.loop_stop()
5047

5148
async def handle_notification_messages(channel: redis.client.PubSub):
5249
pass
5350

54-
async def subscribe(redis_url: str):
51+
async def subscribe():
52+
ctx = click.get_current_context()
53+
redis_url = ctx.obj['redis_url']
54+
logger.debug('Creating event queue subscribers...')
5555
update = asyncio.create_task(redis.subscribe('device:state:update', handle_state_messages, redis_url))
5656
create = asyncio.create_task(redis.subscribe('device:state:create', handle_state_messages, redis_url))
5757
p_all = asyncio.create_task(redis.subscribe('notification:all', handle_notification_messages, redis_url))
@@ -61,7 +61,7 @@ async def subscribe(redis_url: str):
6161
await p_all
6262
await p_mqtt
6363

64-
def run(redis_url: str):
64+
def run():
6565
"""Begins monitoring of queue to publish events to mqtt
6666
6767
Note: Events Monitored
@@ -70,10 +70,7 @@ def run(redis_url: str):
7070
- `notification:all`
7171
- `notification:mqtt`
7272
73-
Args:
74-
redis_url: The connection string to the redis instance in URL form
75-
publisher: MQTT connection details
7673
"""
77-
78-
asyncio.run(subscribe(redis_url))
74+
logger.info("Starting iot-state service(s)...")
75+
asyncio.run(subscribe())
7976

src/kc3zvd/iot_state/subscribers/redis.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
from typing import Callable
33
import redis.asyncio as redis
44
import asyncio
5+
import logging
6+
logger = logging.getLogger(__name__)
57

68
async def subscribe(channel: str, callback: Callable[[], None], redis_url: str):
79
client = redis.Redis.from_url(redis_url)
810

9-
print(f"Subscribing to channel: {channel}")
11+
message = f"Subscribing to channel: {channel}"
12+
logger.info(message)
1013
async with client.pubsub() as pubsub:
1114
await pubsub.subscribe(channel)
1215
future = asyncio.create_task(callback(pubsub))

src/kc3zvd/iot_state/subscribers/wled.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,7 @@ async def async_close(self) -> None:
7676

7777
def listen() -> None:
7878
# Set up logging
79-
logger.setLevel(level=logging.DEBUG)
80-
handler = logging.StreamHandler(sys.stdout)
81-
handler.setLevel(level=logging.DEBUG)
82-
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
83-
handler.setFormatter(formatter)
84-
logger.addHandler(handler)
79+
8580

8681
# set up event loop
8782
loop = asyncio.get_event_loop()

src/kc3zvd/iot_state/utility.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
from __future__ import annotations
2+
3+
14
def normalize(content: str) -> str:
2-
return content.lower().replace(" ", "_")
5+
return content.lower().replace(" ", "_")

0 commit comments

Comments
 (0)