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

129
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")
space_state = msg.payload.decode("ascii")
if space_state == "open":
await ctx.send("Bitlair is OPEN! :sunglasses:") await ctx.send("Bitlair is OPEN! :sunglasses:")
elif spaceState == "closed": elif space_state == "closed":
await ctx.send("Bitlair is closed :pensive:") 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,21 +107,12 @@ 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()
async def event_task():
retained = { retained = {
"bitlair/alarm", "bitlair/alarm",
"bitlair/photos", "bitlair/photos",
@ -116,47 +120,38 @@ retained = {
"bitlair/state/djo", "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
# post to mqtt discord channel when state changes payload = msg.payload.decode("ascii")
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 if msg.topic.matches("bitlair/alarm"):
# Discord on startup. webhook_message(f"Alarm: {payload}")
if topic in retained: elif msg.topic.matches("bitlair/state"):
retained.remove(topic) webhook_message(f"Bitlair is now {payload.upper()}")
return elif msg.topic.matches("bitlair/state/djo"):
webhook_message(f"DJO is now {payload.upper()}")
if topic == "bitlair/alarm": elif msg.topic.matches("bitlair/photos"):
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) webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True)
embed = DiscordEmbed(title="WIP Cam", color="fc5d1d") embed = DiscordEmbed(title="WIP Cam", color="fc5d1d")
embed.set_url("https://bitlair.nl/fotos/view/" + msg) embed.set_url(f"https://bitlair.nl/fotos/view/{payload}")
embed.set_image("https://bitlair.nl/fotos/photos/" + msg) embed.set_image(f"https://bitlair.nl/fotos/photos/{payload}")
webhook.add_embed(embed) webhook.add_embed(embed)
webhook.execute() webhook.execute()
else: else:
return continue
sleep(1) # Prevent triggering rate limits. sleep(1) # Prevent triggering rate limits.
except Exception as e:
print(e)
client = mqtt.Client() async def main():
client.on_connect = on_connect t1 = asyncio.create_task(HobbyBot.start(token))
client.on_message = on_message t2 = asyncio.create_task(event_task())
client.connect(mqtt_host, 1883, 60) await asyncio.gather(t1, t2)
# Start mqtt loop and discord bot
client.loop_start()
HobbyBot.run(token)
# Exit when bot crashes asyncio.run(main())
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" },
] ]