From f4e079fb2105559a421b42e993bab829d992bb71 Mon Sep 17 00:00:00 2001 From: OniriCorpe Date: Sun, 1 Sep 2024 06:16:42 +0200 Subject: [PATCH] v1 --- .gitignore | 4 + LICENSE | 29 ++++ README.md | 32 +++++ bisitariak.py | 102 ++++++++++++++ config.py.example | 7 + koloretsua/code.py | 127 ++++++++++++++++++ .../lib/adafruit_connection_manager.mpy | Bin 0 -> 3541 bytes koloretsua/lib/adafruit_minimqtt/__init__.py | 0 .../adafruit_minimqtt/adafruit_minimqtt.mpy | Bin 0 -> 12713 bytes koloretsua/lib/adafruit_minimqtt/matcher.mpy | Bin 0 -> 845 bytes koloretsua/lib/adafruit_ticks.mpy | Bin 0 -> 694 bytes koloretsua/lib/neopixel.mpy | Bin 0 -> 1318 bytes koloretsua/settings.toml.example | 10 ++ requirements.txt | 4 + 14 files changed, 315 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bisitariak.py create mode 100644 config.py.example create mode 100644 koloretsua/code.py create mode 100644 koloretsua/lib/adafruit_connection_manager.mpy create mode 100644 koloretsua/lib/adafruit_minimqtt/__init__.py create mode 100644 koloretsua/lib/adafruit_minimqtt/adafruit_minimqtt.mpy create mode 100644 koloretsua/lib/adafruit_minimqtt/matcher.mpy create mode 100644 koloretsua/lib/adafruit_ticks.mpy create mode 100644 koloretsua/lib/neopixel.mpy create mode 100644 koloretsua/settings.toml.example create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd2e97e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +venv +config.py +settings.toml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c8f9844 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +"i'm so tired" software license 1.0 + +copyright (c) 2024 OniriCorpe + +this is anti-capitalist, anti-bigotry software, made by people who are tired of ill-intended organisations and individuals, and would rather not have those around their creations. + +permission is granted, free of charge, to any user (be they a person or an organisation) obtaining a copy of this software, to use it for personal, commercial, or educational purposes, subject to the following conditions: + +1. the above copyright notice and this permission notice shall be included in all copies or modified versions of this software. + +2. the user is one of the following: + a. an individual person, labouring for themselves + b. a non-profit organisation + c. an educational institution + d. an organization that seeks shared profit for all of its members, and allows non-members to set the cost of their labor + +3. if the user is an organization with owners, then all owners are workers and all workers are owners with equal equity and/or equal vote. + +4. if the user is an organization, then the user is not law enforcement or military, or working for or under either. + +5. the user does not use the software for ill-intentioned reasons, as determined by the authors of the software. said reasons include but are not limited to: + a. bigotry, including but not limited to racism, xenophobia, homophobia, transphobia, ableism, sexism, antisemitism, religious intolerance + b. pedophilia, zoophilia, and/or incest + c. support for cops and/or the military + d. any blockchain-related technology, including but not limited to cryptocurrencies + +6. the user does not promote or engage with any of the activities listed in the previous item, and is not affiliated with any group that promotes or engages with any of such activities. + +this software is provided as is, without any warranty or condition. in no event shall the authors be liable to anyone for any damages related to this software or this license, under any kind of legal claim. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7687fa9 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# bisitariak & koloretsua + +those scripts are used to made a lamp who illuminates with various colors (generated based on visitor's IP address) when someone visit my websites (detection based on nginx logs) + +to use them, you need a MQTT broker +if you want to use a public broker: + +'bisitariak' means 'visitors' and 'koloretsua' means 'colorful' in Basque + +## bisitariak + +this script monitors nginx access logs and advertises any new visits to a MQTT broker +it comes with it's sibbling script (`koloretsua`) wich turn on a LED strip for each visitor, using an ESP microcontroller + +TODO: + +- [x] properly detect any new visits + - [ ] FIXME: to test properly +- [x] extract their associated IP address +- [x] generate a color based on this IP address +- [x] advertise new visitors (and their associated color) on the MQTT broker + +## koloretsua + +this script is using [CircuitPython](https://circuitpython.org/) and [MiniMQTT](https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT) + +### install + +- flash an ESP card with CircuitPython (i used a Wemos S2 mini) +- put the files from the `koloretsua/lib` directory in the `lib` directory of your ESP card +- put the `code.py` file in the root of your ESP +- copy the `settings.toml.example` file into the root of your ESP, rename it to `settings.toml` and configure it diff --git a/bisitariak.py b/bisitariak.py new file mode 100644 index 0000000..dfda42d --- /dev/null +++ b/bisitariak.py @@ -0,0 +1,102 @@ +#! /usr/bin/python + +from os import path +from dateutil import parser +import datetime +import json +import re +import hashlib +import paho.mqtt.publish as publish +from watchfiles import Change, watch +import config + + +# config stuff +LOG_DIR = config.LOG_DIR +BROKER_HOST = config.BROKER_HOST +BROKER_PORT = config.BROKER_PORT +BROKER_ACCOUNT = config.BROKER_ACCOUNT +BROKER_PASSWORD = config.BROKER_PASSWORD +BROKER_TOPIC = config.BROKER_TOPIC + + +last_parse = datetime.datetime.now() + + +def filter_logs(change: Change, path: str) -> bool: + return path.endswith("access.log") + + +def parse_nginx_log(log): + lineformat = re.compile( + r"""(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))) - - \[(?P\d{2}\/[a-z]{3}\/\d{4}:\d{2}:\d{2}:\d{2} (\+|\-)\d{4})\] ((\"(GET|POST|HEAD|PUT|DELETE) )(?P.+)(http\/(1\.1|2\.0)")) (?P\d{3}) (?P\d+) (?P-|"([^"]+)") (["](?P[^"]+)["])""", + re.IGNORECASE, + ) + visits = () + global last_parse + + try: + logfile = open(log) + except Exception as e: + print(f"Error while trying to open the logfile: {e}") + + for line in logfile.readlines(): + data = re.search(lineformat, line) + if data: + datadict = data.groupdict() + ip = datadict["ipaddress"] + datetimestring = datadict["dateandtime"] + date = parser.parse(datetimestring, fuzzy=True, ignoretz=True) + + if last_parse > date: + # if a visitors is from the past (before the script launch or already processed), ignore it + continue + + if ip in visits: + # ignore IP adresses that are already seen + continue + + # add the IP address to a tuple + visits = (*visits, ip) + + # save the parsing datetime for later + last_parse = datetime.datetime.now() + + return visits + + +def to_color(ip_address): + # converts an IP (or any string) to a color code (hex value) + + hash = hashlib.shake_256(ip_address.encode(), usedforsecurity=False).digest(3) + # concatenate the 3 hex shit into one ('0x87', '0xc3', '0xd2' to '0x87c3d2') + # #cursedCode + # return hex(((hash[0] << 8) | hash[1]) << 8 | hash[2]) + return (int(hash[0]), int(hash[1]), int(hash[2])) + + +def to_mqtt(payload): + try: + publish.single( + topic=f"{BROKER_TOPIC}/visits", + payload=json.dumps({"color": payload}), + qos=0, + hostname=BROKER_HOST, + port=BROKER_PORT, + auth={"username": BROKER_ACCOUNT, "password": BROKER_PASSWORD}, + client_id="bisitariak", + ) + except Exception as e: + print(f"Error while trying to publish on the broker: {e}") + + +### actual script: + + +for changes in watch(LOG_DIR, watch_filter=filter_logs): + for log in changes: + logfile = log[1] + for ip in parse_nginx_log(logfile): + color = to_color(ip) + to_mqtt(color) + print(f"published color '{color}' for IP '{ip}'") diff --git a/config.py.example b/config.py.example new file mode 100644 index 0000000..b920ed6 --- /dev/null +++ b/config.py.example @@ -0,0 +1,7 @@ +BROKER_HOST = "broker.example.com" # string, IP or domain-name of your mqtt broker +BROKER_PORT = 1883 # int, port of your mqtt broker +BROKER_ACCOUNT = "account-name" # string +BROKER_PASSWORD = "account-password" # string +BROKER_TOPIC = "bisitariak" # string + +LOG_DIR = "/var/log/nginx" # string diff --git a/koloretsua/code.py b/koloretsua/code.py new file mode 100644 index 0000000..273705e --- /dev/null +++ b/koloretsua/code.py @@ -0,0 +1,127 @@ +# somewhat based on https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT/blob/main/examples/native_networking/minimqtt_adafruitio_native_networking.py +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + +import board +import json +from math import log +import neopixel +import os +import socketpool +import ssl +import time +import wifi +import adafruit_minimqtt.adafruit_minimqtt as MQTT + + +# config stuff +BROKER_HOST = os.getenv("BROKER_HOST") +BROKER_PORT = os.getenv("BROKER_PORT") +BROKER_ACCOUNT = os.getenv("BROKER_ACCOUNT") +BROKER_PASSWORD = os.getenv("BROKER_PASSWORD") +BROKER_TOPIC = os.getenv("BROKER_TOPIC") +LED_STRIP_NUMBER = os.getenv("LED_STRIP_NUMBER") + +strip = neopixel.NeoPixel(board.IO16, LED_STRIP_NUMBER) +global last_color +last_color = (0, 0, 0) +global strip_on +strip_on = 0 + + +print("Connecting to %s" % os.getenv("CIRCUITPY_WIFI_SSID")) +wifi.radio.connect( + os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") +) +print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID")) + + +def color_strip(color): + color = tuple(list(color)) # convert string tuple to tuple tuple + strip.fill(color) + # remember the color + global last_color + last_color = color + # remember the time when the strip was enabled + global strip_on + strip_on = time.monotonic() + + +def fade_color(color): + if color == (0, 0, 0): + return (0, 0, 0) + faded = tuple() + for i in range(len(color)): + if color[i] < 5: + faded_color = 0 + elif color[i] > 0: + faded_color = color[i] / log(color[i]) + faded = faded + (faded_color,) + return faded + + +# Define callback methods which are called when events occur +def connected(client, userdata, flags, rc): + print("Connected to MQTT Broker!") + + +def disconnected(client, userdata, rc): + print("Disconnected from MQTT Broker!") + + +def subscribe(client, userdata, topic, granted_qos): + print(f"Subscribed to {topic} with QOS level {granted_qos}") + + +def unsubscribe(client, userdata, topic, pid): + print(f"Unsubscribed from {topic} with PID {pid}") + + +def on_message(client, topic, message): + print(f"New message by {client} on topic {topic}: {message}") + message = json.loads(message) + if ("color" in message) and (message["color"]): + color_strip(message["color"]) + + +# Create a socket pool +pool = socketpool.SocketPool(wifi.radio) +ssl_context = ssl.create_default_context() + + +# Set up a MiniMQTT Client +client = MQTT.MQTT( + broker=BROKER_HOST, + port=BROKER_PORT, + username=BROKER_ACCOUNT, + password=BROKER_PASSWORD, + socket_pool=pool, + ssl_context=ssl_context, +) + +# Setup the callback methods above +client.on_connect = connected +client.on_disconnect = disconnected +client.on_subscribe = subscribe +client.on_unsubscribe = unsubscribe +client.on_message = on_message + +# Connect the client to the MQTT broker. +print("Connecting to MQTT broker...") +client.connect() + +# Subscribe to the configured topic +client.subscribe(f"{BROKER_TOPIC}/visits", 0) + + +# Start a blocking message loop... +# NOTE: NO code below this loop will execute +while True: + client.loop(timeout=1) + + if time.monotonic() - strip_on < 5: + # turn off the led strip after ~5 seconds + continue + + strip.fill(last_color) + last_color = fade_color(last_color) diff --git a/koloretsua/lib/adafruit_connection_manager.mpy b/koloretsua/lib/adafruit_connection_manager.mpy new file mode 100644 index 0000000000000000000000000000000000000000..9d0c75d96e3fef7f7fc890cbc5de4da9f81ddb4c GIT binary patch literal 3541 zcmaJ?-%}gc6}}P(gbaAuU2glSstxjS)ndwufozhO?_fG$Z_Ux`?u;VE++P!=m6+K>${17Y+;?zA}8JDg$*tnOcrqUqIbDFH{@xoT|&IqV`IqO^)A^P|D#wX?YTR-4Tu@>I{Q&CQI~)ZQjO>W(ZfEkxk7Oi5KXqV5X0YAX+x znolwdOLPPcF}XXrQaQI)J|XN?Wy7p(H>WV)+U0%CpoXXdv+BohmUSoUX9TVYqsR%Z z({e8{JeoqABu~s2L{5NaG3&f2X|wEoofbfCRV3urZ-y3L)4F4zPL&*WRy+msyKxxuz^n~RSG2Qo7`OB zv8upVIYHV+LjqUka-cqeD{ul=VL1_TI|L?`MO{p_@m}@FoG7B)&iGD$En7?Css%Iz zRWPe1N9HOoNGMxdu>i0tFcIR_i@20WrWw6@OE%ajU?ktG9Ck=NCMnLrt|5FxK&kIa(TN} z2s>NOiQ<&~<4=&e?ef?q6P45kPQAD66<)G8m4Zyesr8cB+?3}zQS&y@>@W%AGpSTO ziA=Z-@aks&^@^Rl2Wp0YA$Gklc0kLO;YQAH+g~c#F7ZHReEj22rht)Y+uD<&4D`Oy@D%8S0F!o9edpP(8L@s@HawI-5=T5Z326Qhh0(0mET| z`;64Nl&=jV;DhiYDg(b2_^rY(>uaap@|masUk5dk@^xY)1d)lj$C#WjQx|<*)I@@{ z)aH?;Z_m5i)7MYk!1|~UeTee=4Aiv*Yuv3)*!)G;x_ohH$O(Ui(Yj)tY$(=1op?vF zzEdZzDAp@=;$6l1Zk?D?tW$O3J;nMSUaDj7C;V-S^?h}DRa;(7u%E5|V8pH!e=Seh z2h%g%3NcAut1CnxU}RVD!V5b4Bz6|xz}tuq@N&a%@EaS2#1psi)~w-Hg01DvW_&M{ z*LdPO&xFU2oG?(=f$RE!#b3N7hk;=*!S2lb(STnsx@P4W`{*40D}@MRA-|EH!}}m% zkbpIdvktT0!0ng^@pCtC$;+}O7KKPh8^lEwlBx#`SsbtX4eUMCLG0;?3F<>YN0Z%e zF>+;aVWtDWtw6*Io_j$@vkGA)`dJ|R`%7Dy0Y=`E%}%$|8Hu=J&ZukF6)gq_T`s%J zIXxKR!^1J>aAbXccsPQ;1t5Q}_nrlJ zEqj)HaPPu;a6P!&yW9DBr`B*%Z+Kj9_$AQ&jMn@%D%O|xwfha!9K22No(H*4`#Pyb z@OzP?1M{rKvC*{z=3N8X#}jPVj(Gs{7jMdcppum{hj$pSC`8zAc+z0zEZ->P^gd`K54G6Ah66_bj|C&6pV)Y}zGwPN9v*;M53(>)gv(Au0TqRv~IbE-%1IA~E z^wLA}`VltU2<#Z1HJ*p2>z}6O^z$R~odaMn(|tre@w|n0Nqed?2kzd~a(^q?P zFdi>{By8P*8a(jGJq~Bl<#dgN2FuPtXVmQq50>4I^02e)4js_bc=-TITyN|jc-o&o z0uDdY1$aJ6fB#q`GN%!F3`8EYz}rH)_v?*78_?g!6329C`RPM?_9z?$g`NSUTIc0@ zLxwlu47AJXA7&_pjD1~i96me>bbPOnGmjnzi~y#U6)$+&UeGKI_#AyxAumC7-yEHX z;s4$Gzh#5x;!W;BK{h+4qr)S^(OE~x?HGwE^t3W+coK@@8FDOuR6Q+z1MBhqM8Np% zfi`0WM<)IVRl-J3^-LwNcj|GSMBee bxcT6V+pZA>&p;nzI0>VMXOs;5((L~L*;;Yq literal 0 HcmV?d00001 diff --git a/koloretsua/lib/adafruit_minimqtt/__init__.py b/koloretsua/lib/adafruit_minimqtt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/koloretsua/lib/adafruit_minimqtt/adafruit_minimqtt.mpy b/koloretsua/lib/adafruit_minimqtt/adafruit_minimqtt.mpy new file mode 100644 index 0000000000000000000000000000000000000000..1057d4f74965a90058ceb88cbe86c1eeb348ee7a GIT binary patch literal 12713 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 CCl0Ov literal 0 HcmV?d00001 diff --git a/koloretsua/lib/adafruit_minimqtt/matcher.mpy b/koloretsua/lib/adafruit_minimqtt/matcher.mpy new file mode 100644 index 0000000000000000000000000000000000000000..af4eed495d3c0f7c78a423ddc99dd6d139d0e74b GIT binary patch literal 845 zcmZ8dT~FFj7(Oi@77!@3R;D1xR}sXI2FaF~#XxlohqD7_s&SnuRhua|>`Y9Sr63oY z>~{YHq4)ztcfWYEU2Oh`{em5Enq}wa{!oFOV_unhZ z^!}D2?n>3EgJWRFSXorSpmKa5mv;b_;|hh`-7~;eDkTX7%tEzNQNWXWz>15z@?NPb zl|kKO6Dws!Dl5P{d`!xUR2BNRpiZMfV5~|aKw|(6fZTL^&M63odlltS5I~)%#1rftiMc&vYIGqOn`sgQ-E-1}o0EvJ?6_(Z7IIMQlapvS6>qhB zCXLmV5u4kiGCqGW7NNt-nF!QfKIlx{7GY@@FH>_Z!pi+(mz>`71R}vfTyuJF*$m$Y zxb00&JOxL7JM?<$3;5~SL_8OX$HOsjnVI6C=X&?*!FBjr(+Eqa2(cE4w>w%?mqhp! z&1o)c*8ZQd)7MJC2IDij?)4Rvr88P@l^b@6hbQ$M7VG3UUZ*_p1Lm_|5QlqQU0ImO zezv1J{ohC$Jtv(wkwk82of<{*t+qA}bhJLChnpo35jn%ZErvkRR;J_Fd>Aym$E2rx4JJ*BzMslU)s=y&Z~L?RNrw!|n3;P#<3pA?Za; i{mycI0zUK)gX%Lj?isJ(h*L$v`t_pMEPb)VbK)PJv7`-hkl!C17*aZiPq}zi5L#1hAT+|>#vP?81#$a7cWu=TpprkD`ix1u+ zamoBMuydvb&%|uJ<~CJhNkvxz%Lo#eN&^^1aTiW zjaF-cJGyNHsH(@hWgBKsRROE2mfklpdU4>2yuZnO=7zcqosUrC|2c3UGhQ}entI1+ z04?g#0`PwcP>)p@yKx6gTuULcE!cD;?u?1JBFp<^Y>4jHeSZd$YTSb5+2SN zJqMrN=wwX1aI0f}1mCuPklV!j42oB`MkzteRbGDOh3d0Y?q2nbAB9FVV#zF#3ZzL$ zD(nipwFzKsH-=aWSA%H^Qp(<8om=JDEO}Y}i4;^zg z`jmN?6J`h@FsUC*D`CN%<7uh%Rw+LpPp*XFod4DM+na<;=)=CiMmADtSj;^bPp)s$ fF3x@1)eZOA^~Jg7(n2(f`_GDeWIT~cG}ZnAK_1z^ literal 0 HcmV?d00001 diff --git a/koloretsua/lib/neopixel.mpy b/koloretsua/lib/neopixel.mpy new file mode 100644 index 0000000000000000000000000000000000000000..e79666f32d940cbbc51a02556fc36be931333279 GIT binary patch literal 1318 zcmY*WT~ixX7=AaOOB&(iWH|&9X-XiJ010eES|PTOY|4iS1VPMbXGt~-*)e2yHk(53 zHmM)Fxwf2Ce<{qF8S>5V@1k}A!bfKf5X}}e9<*;N_*7P>g&4orxZO#Sc6G`q$A$F*S;s5-H`EpGLO!r&}0*yU4mF|Gj(u~>fl?+3x z&c|X@^ir)5rTb%7GRKy#^iC_fM0MY)8zr?OUda-rJ-}LLqciq_D;~wI&ArUFxVrIf zW_u?ifW7zs8F;)t6=zJR=?sU%>-SUq)TT6A431uW`)1Z}Ar^XCiItujav!MO3cR}1Sjxm)SN%uubRUju|@iyI*a?@xlQgM zOx}2B3LYV@5Moe&VH=S`77Y|yH-I5No+0DBg+zEOndEI`$}`W~;|!VM9b}ew622v| zpOwG*!aQAE^erSqGh9CzF!fM$BD^*loZg6xh9aSn$Zon8jGU!6zJK&m5O7~Ag*`7) zIsE1sz8kj)TRfb_nP1T$H}q42d68PowtaqV@dmuFo4cR>Dg0sLd)SJGvvKRwm%=Yy zFJz(KZ|B*+em_sIbf#3xLy)AqE}7Q!~~sCTUG4f(>9y~fNEduze0O+rh2tLHgJ?cErcnvEp%DH7CNyon literal 0 HcmV?d00001 diff --git a/koloretsua/settings.toml.example b/koloretsua/settings.toml.example new file mode 100644 index 0000000..a707cf1 --- /dev/null +++ b/koloretsua/settings.toml.example @@ -0,0 +1,10 @@ +CIRCUITPY_WIFI_SSID = "WIFI_SSID" +CIRCUITPY_WIFI_PASSWORD = "WIFI_PASSWORD" + +BROKER_HOST = "broker.example.com" # string, IP or domain-name of your mqtt broker +BROKER_PORT = 1883 # int, port of your mqtt broker +BROKER_ACCOUNT = "account-name" # string +BROKER_PASSWORD = "account-password" # string +BROKER_TOPIC = "bisitariak" # string + +LED_STRIP_NUMBER = 8 # int, number of LED on the strip diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ef1a41f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +python-dateutil +re +paho-mqtt +watchfiles