diff --git a/rule-listener/__pycache__/script.cpython-312.pyc b/rule-listener/__pycache__/script.cpython-312.pyc new file mode 100644 index 0000000..dcc9029 Binary files /dev/null and b/rule-listener/__pycache__/script.cpython-312.pyc differ diff --git a/rule-listener/data.py b/rule-listener/data.py index 4315d98..0013479 100644 --- a/rule-listener/data.py +++ b/rule-listener/data.py @@ -1,47 +1,103 @@ +topics = [ + "main/fan/one", + "main/fan/two", + "main/printer/one", + "main/printer/two", + "main/lamp/one", + "main/radio/one"] +walkDelay = 1 +baseRule = { + "state": 1, + "count": 1, + "doMessage": 1, + "delay": 30, + "reset": False, + "doTopic": "" +} rules = { + "main/printer/one": [ + { + "description": "Every time a printer is turned on, plan the gesture for 5 minutes later", + "count": 0, # the amount of times + "state": 1, # it has had this state + "doTopic": "main/gesture/printer/one", # i will send to topic + "doMessage": 1, # this message + "delay": 60 * 5, # after this amount of seconds + "reset": True, # and i should reset my count afterwards + }, + ], + "main/printer/two": [], + "main/lamp/one": [ + { + "description": "after the lamp has been on 2 times, another fan should be turned on", + "count": 3, # the amount of times + "state": 1, # it has had this state + "doTopic": "main/fan/two", # i will send to topic + "doMessage": 1, # this message + "delay": False, # after this amount of seconds + "reset": True, # and i should reset my count afterwards + }, + ], "main/fan/one": [ - { # after the fan has been on 2 times, another fan should be turned on - "count": 2, # the amount of times - "state": "on", # it has had this state - "doTopic": "main/fan/two", # i will send to topic - "doMessage": "on", # this message - "delay": False, # after this amount of seconds - "reset": True, # and i should reset my count afterwards + { + "description": "after the fan has been on 2 times, another fan should be turned on", + "count": 2, # the amount of times + "state": 1, # it has had this state + "doTopic": "main/fan/two", # i will send to topic + "doMessage": 1, # this message + "delay": False, # after this amount of seconds + "reset": True, # and i should reset my count afterwards }, - { # After the fan is turned off, the next fan should be turned on - "count": 4, # the amount of times - "state": "off", # it has had this state - "doTopic": "main/fan/two", # i will send to topic - "doMessage": "on", # this message - "delay": 4, # after this amount of seconds - "reset": True, # and i should reset my count afterwards + # { # After the fan is turned off, the next fan should be turned on + # "count": 4, # the amount of times + # "state": 0, # it has had this state + # "doTopic": "main/fan/two", # i will send to topic + # "doMessage": 1, # this message + # "delay": 4, # after this amount of seconds + # "reset": True, # and i should reset my count afterwards + # }, + # { # Turn the fan off every time after 1 minute + # "count": 0, + # "state": 1, # it has had this state + # "doTopic": "main/fan/three", # i will send to topic + # "doMessage": 0, # this message + # "delay": 4, # after this amount of seconds + # "reset": False, # and i should reset my count afterwards + # }, + # { # Every 5 minutes, do gesture X + # "state": 1, + # "count": 1, + # "doTopic": "gesture/fan/one", + # "doMessage": 1, + # "delay": 5 * 60, + # "reset": True + # }, + + ], + "main/fan/two": [ + { + "description": "after the fan has been on 2 times, another fan should be turned on", + "count": 2, # the amount of times + "state": 1, # it has had this state + "doTopic": "main/fan/one", # i will send to topic + "doMessage": 1, # this message + "delay": False, # after this amount of seconds + "reset": True, # and i should reset my count afterwards }, - { # Turn the fan off every time after 1 minute - "count": False, # the amount of times. If its false, just do it. - "state": "on", # it has had this state - "doTopic": "main/fan/three", # i will send to topic - "doMessage": "off", # this message - "delay": 4, # after this amount of seconds - "reset": False, # and i should reset my count afterwards + + ], + "main/radio/one": [ + { + "description": "after the radio has been on 2 times, another fan should be turned on", + "count": 2, # the amount of times + "state": 1, # it has had this state + "doTopic": "main/fan/two", # i will send to topic + "doMessage": 1, # this message + "delay": 10, # after this amount of seconds + "reset": True, # and i should reset my count afterwards }, - { # Turn the fan on every time after 6 minute - "count": 3, # the amount of times. If its false, just do it. - "state": "on", # it has had this state - "doTopic": "main/fan/three", # i will send to topic - "doMessage": "off", # this message - "delay": 360, # after this amount of seconds - "reset": False, # and i should reset my count afterwards - }, - { # Every 5 minutes, do gesture X - "count": 1, - "state": "on", - "doTopic": "gesture/fan/one", - "doMessage": "on", - "delay": 5 * 60, - "reset": True - } - ] + ], } @@ -53,4 +109,4 @@ gestures = { "multiplier": 0.7, "range": [4, 8] } -} \ No newline at end of file +} diff --git a/rule-listener/script.py b/rule-listener/script.py index 4e1648f..cc76523 100644 --- a/rule-listener/script.py +++ b/rule-listener/script.py @@ -2,108 +2,138 @@ import paho.mqtt.client as mqtt import asyncio import secrets import data -from random import randrange +import random +import pprint + state = {} rules = data.rules mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) -async def do_a_gesture(gesture, topic): - state = False - delay = gesture["delay"] - for i in range(randrange(gesture["range"][0],gesture["range"][1])): - await asyncio.sleep(delay) - delay = delay * gesture["multiplier"] - if state: - publish("gesture" + topic, gesture["on"]) - state = False - else: - publish("gesture" + topic, gesture["off"]) - state = True +def publish_transform(fromTopic, toTopic): + if fromTopic and toTopic: + ft = fromTopic.removeprefix("main") + tt = toTopic.removeprefix("main") + path = "transform" + ft + tt + publish(path, 1) + + +async def do_a_gesture(topic): + print(f"called to do a gesture for {topic}") + # TODO: make sure the data.topics array is shuffled ánd the send in topic is always first + tl = len(data.topics) + theseTopics = data.topics[random.randint(0, tl / 2):random.randint(tl/2, tl)] + wIndex = random.randint(1, tl) + isW = True + print(theseTopics) + + while isW: + wIndex+=1 + index = wIndex%len(theseTopics) + publish(theseTopics[index], 1) + publish_transform(theseTopics[index - 1], theseTopics[index]) + await asyncio.sleep(data.walkDelay) + + if index > 0: + publish(theseTopics[index - 1], 0) + print(f"Go off: {theseTopics[index - 1]}") + await asyncio.sleep(data.walkDelay * 0.8) + + if(random.random() > 0.8): + # TODO send message that gesture is finished + print("finished the walk") + publish("main/gesture" + topic, 0) + isW = False + - publish("main/gesture" + topic, 0) - def on_connect(client, userdata, flags, reason_code, properties): print(f"Connected with result code {reason_code}") client.subscribe("main/#") + async def publish_later(rule, transformTopic): - print(f"publish_later: Processing message with doTopic {rule['doTopic']} after {rule['delay']} seconds delay") + print( + f"publish_later: Processing message with doTopic {rule['doTopic']} after {rule['delay']} seconds delay") await asyncio.sleep(rule['delay']) del rule['doingDelay'] publish(rule['doTopic'], rule['doMessage']) publish(transformTopic, 1) + def publish(topic, msg): - print(f"publish {topic} {msg}") result = mqttc.publish(topic, msg) - status = result[0] - if not status == 0: print(f"Failed to send message to topic {topic}") + def check_against_rules(msg): if msg.topic in rules: - print(f"Found {len(rules[msg.topic])} rules on this topic"), - + print(f"Found {len(rules[msg.topic])} rules on {msg.topic}"), willReset = False - - for rule in rules[msg.topic]: - print(f"Rule: when the count is > {rule['count']}. Current = {state[msg.topic]['count'][msg.payload]} on payload {msg.payload}"), - if (state[msg.topic]['count'][msg.payload] >= rule['count'] or rule['count'] == False) and msg.payload == rule['state']: - print(f"transform topic is {transformTopic}") - if(rule['reset']): + for rule in rules[msg.topic]: + if (state[msg.topic]['count'][msg.payload] >= rule['count'] + or rule['count'] is None) and int(float(msg.payload)) == rule['state']: + + transformTopic = f"transform" + \ + msg.topic.removeprefix("main") + rule['doTopic'].removeprefix("main") + print(f"---- rule is met----") + print(rule) + + if (rule['reset']): willReset = True if rule['delay']: - if not 'doingDelay' in rule: + if 'doingDelay' not in rule: rule['doingDelay'] = True asyncio.run_coroutine_threadsafe(publish_later(rule, transformTopic), loop) - else: + else: print('not scheduling') else: publish(transformTopic, 1) publish(rule['doTopic'], rule['doMessage']) - + if willReset: state[msg.topic]['count'][msg.payload] = 0 - + else: print("... ... i have no rules for this topic") - + + # The callback for when a PUBLISH message is received from the server. def on_message(client, userdata, msg): - print("on message") if msg.retain: return False else: msg.payload = str(msg.payload.decode("utf-8")) - + print(f'msg: {msg.payload} topic: {msg.topic}') + if len(msg.payload) > 140: print("way to long of a message, not gonna store that") return False - + if msg.topic not in state: state[msg.topic] = {'count': {}} - + state[msg.topic]['count'].setdefault(msg.payload, 0) state[msg.topic]['count'][msg.payload] += 1 # check if the message has a gesture if "gesture" in msg.topic: - if msg.payload.strip() in data.gestures: - asyncio.run_coroutine_threadsafe(do_a_gesture(data.gestures[msg.payload], msg.topic.removeprefix('main/gesture')), loop) + if msg.payload.strip() != 0: + asyncio.run_coroutine_threadsafe(do_a_gesture(msg.topic.removeprefix('main/gesture')), loop) else: check_against_rules(msg) + async def main(): global loop loop = asyncio.get_running_loop() # Store the main event loop + mqttc.on_connect = on_connect mqttc.on_message = on_message @@ -113,9 +143,11 @@ async def main(): # Start the MQTT loop in a background thread mqttc.loop_start() - - # Keep the asyncio event loop running + + # asyncio.run_coroutine_threadsafe(do_a_gesture(data.topics[0].removeprefix('main')), loop) + + # # Keep the asyncio event loop running await asyncio.Event().wait() # Run the main function -asyncio.run(main()) \ No newline at end of file +asyncio.run(main())