Replace paho-mqtt with aiomqtt

This commit is contained in:
polyfloyd 2025-05-05 21:45:01 +02:00
parent 37e349b8d1
commit 07cab2d2d1
3 changed files with 94 additions and 87 deletions

163
main.py
View file

@ -1,17 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Bitlair HobbyBot import asyncio
from time import sleep
from discord import Intents
from discord.ext import commands
from discord_webhook import DiscordWebhook, DiscordEmbed
import pytz
import paho.mqtt.client as mqtt
import paho.mqtt.subscribe as subscribe
import os import os
import sys import sys
from time import sleep
import aiomqtt
import pytz
from discord import Intents
from discord.ext import commands
from discord_webhook import DiscordEmbed, DiscordWebhook
mqtt_host = os.getenv("MQTT_HOST") mqtt_host = os.getenv("MQTT_HOST")
if not mqtt_host: if not mqtt_host:
@ -37,13 +35,11 @@ intents.members = True
HobbyBot = commands.Bot(command_prefix="!", description="Bitlair Bot", intents=intents) HobbyBot = commands.Bot(command_prefix="!", description="Bitlair Bot", intents=intents)
def mqtt_get_one(topic): async def mqtt_get_one(topic, timeout=20):
try: async with asyncio.timeout(timeout):
msg = subscribe.simple(topic, hostname=mqtt_host, keepalive=10) async with aiomqtt.Client(mqtt_host) as mq:
return msg.payload.decode() await mq.subscribe(topic)
except Exception as err: return await anext(mq.messages)
print(err)
return ""
# Define bot commands # Define bot commands
@ -56,35 +52,52 @@ async def on_ready():
@HobbyBot.command(description="Bitlair Space State") @HobbyBot.command(description="Bitlair Space State")
async def state(ctx): async def state(ctx):
async with ctx.typing(): async with ctx.typing():
spaceState = mqtt_get_one("bitlair/state") try:
if spaceState == "open": msg = await mqtt_get_one("bitlair/state")
await ctx.send("Bitlair is OPEN! :sunglasses:") space_state = msg.payload.decode("ascii")
elif spaceState == "closed": if space_state == "open":
await ctx.send("Bitlair is closed :pensive:") await ctx.send("Bitlair is OPEN! :sunglasses:")
elif space_state == "closed":
await ctx.send("Bitlair is closed :pensive:")
except Exception as err:
await ctx.send("Meh, stuk")
raise err
# !co2 # !co2
@HobbyBot.command(description="co2 levels") @HobbyBot.command(description="co2 levels")
async def co2(ctx): async def co2(ctx):
async with ctx.typing(): async with ctx.typing():
hoofdruimte = mqtt_get_one("bitlair/climate/hoofdruimte_ingang/co2_ppm") try:
await ctx.send("Hoofdruimte: %s ppm\n" % hoofdruimte) msg = await mqtt_get_one("bitlair/climate/hoofdruimte/co2_ppm")
await ctx.send(f"Hoofdruimte: {msg.payload.decode('ascii')} ppm\n")
except Exception as err:
await ctx.send("Meh, stuk")
raise err
# !temp # !temp
@HobbyBot.command(description="Temperature") @HobbyBot.command(description="Temperature")
async def temp(ctx): async def temp(ctx):
async with ctx.typing(): async with ctx.typing():
hoofdruimte = mqtt_get_one("bitlair/climate/hoofdruimte_ingang/temperature_c") try:
await ctx.send("Hoofdruimte: %s °C\n" % hoofdruimte) msg = await mqtt_get_one("bitlair/climate/hoofdruimte/temperature_c")
await ctx.send(f"Hoofdruimte: {msg.payload.decode('ascii')} °C\n")
except Exception as err:
await ctx.send("Meh, stuk")
raise err
# !humid # !humid
@HobbyBot.command(description="Humidity") @HobbyBot.command(description="Humidity")
async def humid(ctx): async def humid(ctx):
async with ctx.typing(): async with ctx.typing():
hoofdruimte = mqtt_get_one("bitlair/climate/hoofdruimte_ingang/humidity_pct") try:
await ctx.send("Hoofdruimte: %s pct\n" % hoofdruimte) msg = await mqtt_get_one("bitlair/climate/hoofdruimte/humidity_pct")
await ctx.send(f"Hoofdruimte: {msg.payload.decode('ascii')} pct\n")
except Exception as err:
await ctx.send("Meh, stuk")
raise err
# !np # !np
@ -94,69 +107,51 @@ async def np(ctx):
await ctx.send("Now playing: Darude - Sandstorm") await ctx.send("Now playing: Darude - Sandstorm")
# define mqtt client stuff
#
# subscribe to topics
def on_connect(client, userdata, flags, rc):
client.subscribe("bitlair/alarm")
client.subscribe("bitlair/state")
client.subscribe("bitlair/state/djo")
client.subscribe("bitlair/photos")
def webhook_message(msg): def webhook_message(msg):
webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True, content=msg) webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True, content=msg)
webhook.execute() webhook.execute()
retained = { async def event_task():
"bitlair/alarm", retained = {
"bitlair/photos", "bitlair/alarm",
"bitlair/state", "bitlair/photos",
"bitlair/state/djo", "bitlair/state",
} "bitlair/state/djo",
}
async with aiomqtt.Client(mqtt_host) as mq:
await asyncio.gather(*[mq.subscribe(topic) for topic in retained])
async for msg in mq.messages:
# Retained messages trigger an initial message on connecting. Prevent relaying them to Discord on startup.
if str(msg.topic) in retained:
retained.remove(str(msg.topic))
continue
payload = msg.payload.decode("ascii")
if msg.topic.matches("bitlair/alarm"):
webhook_message(f"Alarm: {payload}")
elif msg.topic.matches("bitlair/state"):
webhook_message(f"Bitlair is now {payload.upper()}")
elif msg.topic.matches("bitlair/state/djo"):
webhook_message(f"DJO is now {payload.upper()}")
elif msg.topic.matches("bitlair/photos"):
webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True)
embed = DiscordEmbed(title="WIP Cam", color="fc5d1d")
embed.set_url(f"https://bitlair.nl/fotos/view/{payload}")
embed.set_image(f"https://bitlair.nl/fotos/photos/{payload}")
webhook.add_embed(embed)
webhook.execute()
else:
continue
sleep(1) # Prevent triggering rate limits.
# post to mqtt discord channel when state changes async def main():
def on_message(client, userdata, msg): t1 = asyncio.create_task(HobbyBot.start(token))
try: t2 = asyncio.create_task(event_task())
topic = msg.topic await asyncio.gather(t1, t2)
msg = msg.payload.decode()
# Retained messages trigger an initial message on connecting. Prevent relaying them to
# Discord on startup.
if topic in retained:
retained.remove(topic)
return
if topic == "bitlair/alarm":
webhook_message("Alarm: %s" % msg)
elif topic == "bitlair/state":
webhook_message("Bitlair is now %s" % msg.upper())
elif topic == "bitlair/state/djo":
webhook_message("DJO is now %s" % msg.upper())
elif topic == "bitlair/photos":
webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True)
embed = DiscordEmbed(title="WIP Cam", color="fc5d1d")
embed.set_url("https://bitlair.nl/fotos/view/" + msg)
embed.set_image("https://bitlair.nl/fotos/photos/" + msg)
webhook.add_embed(embed)
webhook.execute()
else:
return
sleep(1) # Prevent triggering rate limits.
except Exception as e:
print(e)
client = mqtt.Client() asyncio.run(main())
client.on_connect = on_connect
client.on_message = on_message
client.connect(mqtt_host, 1883, 60)
# Start mqtt loop and discord bot
client.loop_start()
HobbyBot.run(token)
# Exit when bot crashes
client.loop_stop(force=True)

View file

@ -5,8 +5,8 @@ description = "Bitlair Discord Bot"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"aiomqtt>=2.4.0",
"discord-py>=2.5.2", "discord-py>=2.5.2",
"discord-webhook>=1.4.1", "discord-webhook>=1.4.1",
"paho-mqtt>=2.1.0",
"pytz>=2025.2", "pytz>=2025.2",
] ]

16
uv.lock generated
View file

@ -64,6 +64,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/3c/143831b32cd23b5263a995b2a1794e10aa42f8a895aae5074c20fda36c07/aiohttp-3.11.18-cp313-cp313-win_amd64.whl", hash = "sha256:bdd619c27e44382cf642223f11cfd4d795161362a5a1fc1fa3940397bc89db01", size = 437658, upload-time = "2025-04-21T09:42:29.209Z" }, { url = "https://files.pythonhosted.org/packages/1e/3c/143831b32cd23b5263a995b2a1794e10aa42f8a895aae5074c20fda36c07/aiohttp-3.11.18-cp313-cp313-win_amd64.whl", hash = "sha256:bdd619c27e44382cf642223f11cfd4d795161362a5a1fc1fa3940397bc89db01", size = 437658, upload-time = "2025-04-21T09:42:29.209Z" },
] ]
[[package]]
name = "aiomqtt"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "paho-mqtt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/45/9a/863bc34c64bc4acb9720a9950bfc77d6f324640cdf1f420bb5d9ee624975/aiomqtt-2.4.0.tar.gz", hash = "sha256:ab0f18fc5b7ffaa57451c407417d674db837b00a9c7d953cccd02be64f046c17", size = 82718, upload-time = "2025-05-03T20:21:27.748Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/0c/2720665998d97d3a9521c03b138a22247e035ba54c4738e934da33c68699/aiomqtt-2.4.0-py3-none-any.whl", hash = "sha256:721296e2b79df5f6c7c4dfc91700ae0166953a4127735c92637859619dbd84e4", size = 15908, upload-time = "2025-05-03T20:21:26.337Z" },
]
[[package]] [[package]]
name = "aiosignal" name = "aiosignal"
version = "1.3.2" version = "1.3.2"
@ -174,17 +186,17 @@ name = "discord-bot"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "aiomqtt" },
{ name = "discord-py" }, { name = "discord-py" },
{ name = "discord-webhook" }, { name = "discord-webhook" },
{ name = "paho-mqtt" },
{ name = "pytz" }, { name = "pytz" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "aiomqtt", specifier = ">=2.4.0" },
{ name = "discord-py", specifier = ">=2.5.2" }, { name = "discord-py", specifier = ">=2.5.2" },
{ name = "discord-webhook", specifier = ">=1.4.1" }, { name = "discord-webhook", specifier = ">=1.4.1" },
{ name = "paho-mqtt", specifier = ">=2.1.0" },
{ name = "pytz", specifier = ">=2025.2" }, { name = "pytz", specifier = ">=2025.2" },
] ]