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
# Bitlair HobbyBot
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 asyncio
import os
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")
if not mqtt_host:
@ -37,13 +35,11 @@ intents.members = True
HobbyBot = commands.Bot(command_prefix="!", description="Bitlair Bot", intents=intents)
def mqtt_get_one(topic):
try:
msg = subscribe.simple(topic, hostname=mqtt_host, keepalive=10)
return msg.payload.decode()
except Exception as err:
print(err)
return ""
async def mqtt_get_one(topic, timeout=20):
async with asyncio.timeout(timeout):
async with aiomqtt.Client(mqtt_host) as mq:
await mq.subscribe(topic)
return await anext(mq.messages)
# Define bot commands
@ -56,35 +52,52 @@ async def on_ready():
@HobbyBot.command(description="Bitlair Space State")
async def state(ctx):
async with ctx.typing():
spaceState = mqtt_get_one("bitlair/state")
if spaceState == "open":
await ctx.send("Bitlair is OPEN! :sunglasses:")
elif spaceState == "closed":
await ctx.send("Bitlair is closed :pensive:")
try:
msg = await mqtt_get_one("bitlair/state")
space_state = msg.payload.decode("ascii")
if space_state == "open":
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
@HobbyBot.command(description="co2 levels")
async def co2(ctx):
async with ctx.typing():
hoofdruimte = mqtt_get_one("bitlair/climate/hoofdruimte_ingang/co2_ppm")
await ctx.send("Hoofdruimte: %s ppm\n" % hoofdruimte)
try:
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
@HobbyBot.command(description="Temperature")
async def temp(ctx):
async with ctx.typing():
hoofdruimte = mqtt_get_one("bitlair/climate/hoofdruimte_ingang/temperature_c")
await ctx.send("Hoofdruimte: %s °C\n" % hoofdruimte)
try:
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
@HobbyBot.command(description="Humidity")
async def humid(ctx):
async with ctx.typing():
hoofdruimte = mqtt_get_one("bitlair/climate/hoofdruimte_ingang/humidity_pct")
await ctx.send("Hoofdruimte: %s pct\n" % hoofdruimte)
try:
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
@ -94,69 +107,51 @@ async def np(ctx):
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):
webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True, content=msg)
webhook.execute()
retained = {
"bitlair/alarm",
"bitlair/photos",
"bitlair/state",
"bitlair/state/djo",
}
async def event_task():
retained = {
"bitlair/alarm",
"bitlair/photos",
"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
def on_message(client, userdata, msg):
try:
topic = msg.topic
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)
async def main():
t1 = asyncio.create_task(HobbyBot.start(token))
t2 = asyncio.create_task(event_task())
await asyncio.gather(t1, t2)
client = mqtt.Client()
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)
asyncio.run(main())

View file

@ -5,8 +5,8 @@ description = "Bitlair Discord Bot"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"aiomqtt>=2.4.0",
"discord-py>=2.5.2",
"discord-webhook>=1.4.1",
"paho-mqtt>=2.1.0",
"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" },
]
[[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]]
name = "aiosignal"
version = "1.3.2"
@ -174,17 +186,17 @@ name = "discord-bot"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "aiomqtt" },
{ name = "discord-py" },
{ name = "discord-webhook" },
{ name = "paho-mqtt" },
{ name = "pytz" },
]
[package.metadata]
requires-dist = [
{ name = "aiomqtt", specifier = ">=2.4.0" },
{ name = "discord-py", specifier = ">=2.5.2" },
{ name = "discord-webhook", specifier = ">=1.4.1" },
{ name = "paho-mqtt", specifier = ">=2.1.0" },
{ name = "pytz", specifier = ">=2025.2" },
]