fix: migrate from hbmqtt to amqtt for Python 3.12 compatibility
hbmqtt is abandoned and incompatible with Python 3.12+ due to use of removed APIs (@asyncio.coroutine, asyncio.coroutines._DEBUG). amqtt is the actively maintained fork and a drop-in replacement. Changes: - Replace hbmqtt imports with amqtt equivalents in mqttserver.py - Update logger names from hbmqtt.* to amqtt.* in __init__.py - Map amqtt.broker.BrokerException to Exception (removed in amqtt) - Migrate BumperMQTTServer_Plugin to subclass BaseAuthPlugin with a Config dataclass, matching amqtt's plugin API - Replace pkg_resources entry point registration with amqtt's config-based "plugins" dict format - Fix "ssl": "on" → "ssl": True (amqtt validates types strictly) - Update authenticate() signature to keyword-only session parameter - Add **kwargs to on_broker_client_connected/disconnected callbacks Tested and confirmed working on Python 3.12 with OZMO 920 (vi829v).
This commit is contained in:
parent
df36bdd29d
commit
bc8c40e37d
2 changed files with 44 additions and 43 deletions
|
|
@ -125,7 +125,7 @@ else:
|
|||
translog.addHandler(logging.StreamHandler(sys.stdout))
|
||||
translog.setLevel(logging.CRITICAL + 1) # Ignore this logger
|
||||
logging.getLogger("passlib").setLevel(logging.CRITICAL + 1) # Ignore this logger
|
||||
brokerlog = logging.getLogger("hbmqtt.broker")
|
||||
brokerlog = logging.getLogger("amqtt.broker")
|
||||
#brokerlog.setLevel(
|
||||
# logging.CRITICAL + 1
|
||||
#) # Ignore this logger #There are some sublogs that could be set if needed (.plugins)
|
||||
|
|
@ -133,7 +133,7 @@ if not log_to_stdout:
|
|||
brokerlog.addHandler(mqtt_rotate)
|
||||
else:
|
||||
brokerlog.addHandler(logging.StreamHandler(sys.stdout))
|
||||
protolog = logging.getLogger("hbmqtt.mqtt.protocol")
|
||||
protolog = logging.getLogger("amqtt.mqtt.protocol")
|
||||
#protolog.setLevel(
|
||||
# logging.CRITICAL + 1
|
||||
#) # Ignore this logger
|
||||
|
|
@ -141,7 +141,7 @@ if not log_to_stdout:
|
|||
protolog.addHandler(mqtt_rotate)
|
||||
else:
|
||||
protolog.addHandler(logging.StreamHandler(sys.stdout))
|
||||
clientlog = logging.getLogger("hbmqtt.client")
|
||||
clientlog = logging.getLogger("amqtt.client")
|
||||
#clientlog.setLevel(logging.CRITICAL + 1) # Ignore this logger
|
||||
if not log_to_stdout:
|
||||
clientlog.addHandler(mqtt_rotate)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@
|
|||
import logging
|
||||
import asyncio
|
||||
import os
|
||||
import hbmqtt
|
||||
from hbmqtt.broker import Broker
|
||||
from hbmqtt.client import MQTTClient
|
||||
from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
|
||||
import pkg_resources
|
||||
import amqtt as hbmqtt
|
||||
import amqtt.broker
|
||||
from amqtt.broker import Broker
|
||||
from amqtt.client import MQTTClient
|
||||
from amqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
|
||||
from amqtt.plugins.base import BaseAuthPlugin
|
||||
from dataclasses import dataclass, field
|
||||
amqtt.broker.BrokerException = Exception # amqtt removed this; map to generic Exception
|
||||
import time
|
||||
import bumper
|
||||
import json
|
||||
|
|
@ -183,60 +186,60 @@ class MQTTServer:
|
|||
elif key == "allow_anonymous":
|
||||
allow_anon = kwargs["allow_anonymous"] # Set to True to allow anonymous authentication
|
||||
|
||||
# The below adds a plugin to the hbmqtt.broker.plugins without having to futz with setup.py
|
||||
distribution = pkg_resources.Distribution("hbmqtt.broker.plugins")
|
||||
bumper_plugin = pkg_resources.EntryPoint.parse(
|
||||
"bumper = bumper.mqttserver:BumperMQTTServer_Plugin", dist=distribution
|
||||
)
|
||||
distribution._ep_map = {"hbmqtt.broker.plugins": {"bumper": bumper_plugin}}
|
||||
pkg_resources.working_set.add(distribution)
|
||||
|
||||
# Initialize bot server
|
||||
# Initialize bot server using amqtt's config-based plugin format
|
||||
self.default_config = {
|
||||
"listeners": {
|
||||
"default": {"type": "tcp"},
|
||||
"tls1": {
|
||||
"bind": "{}:{}".format(address[0], address[1]),
|
||||
"ssl": "on",
|
||||
"ssl": True,
|
||||
"certfile": bumper.server_cert,
|
||||
"keyfile": bumper.server_key,
|
||||
},
|
||||
"wss1": {
|
||||
"type": "ws",
|
||||
"bind": "0.0.0.0:8884",
|
||||
"ssl": True,
|
||||
"certfile": bumper.server_cert,
|
||||
"keyfile": bumper.server_key,
|
||||
},
|
||||
},
|
||||
"sys_interval": 0,
|
||||
"auth": {
|
||||
"allow-anonymous": allow_anon,
|
||||
"password-file": passwd_file,
|
||||
"plugins": ["bumper"], # Bumper plugin provides auth and handling of bots/clients connecting
|
||||
"plugins": {
|
||||
"bumper.mqttserver.BumperMQTTServer_Plugin": {
|
||||
"allow_anonymous": allow_anon,
|
||||
"password_file": passwd_file,
|
||||
}
|
||||
},
|
||||
"topic-check": {"enabled": False},
|
||||
}
|
||||
|
||||
self.broker = hbmqtt.broker.Broker(config=self.default_config)
|
||||
self.broker = amqtt.broker.Broker(config=self.default_config)
|
||||
|
||||
except Exception as e:
|
||||
mqttserverlog.exception("{}".format(e))
|
||||
|
||||
|
||||
class BumperMQTTServer_Plugin:
|
||||
class BumperMQTTServer_Plugin(BaseAuthPlugin):
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
allow_anonymous: bool = False
|
||||
password_file: str = ""
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
super().__init__(context)
|
||||
try:
|
||||
self.auth_config = self.context.config["auth"]
|
||||
self._users = dict()
|
||||
cfg = context.config
|
||||
self._allow_anonymous = getattr(cfg, "allow_anonymous", False)
|
||||
self._password_file = getattr(cfg, "password_file", None)
|
||||
self._read_password_file()
|
||||
|
||||
except KeyError:
|
||||
self.context.logger.warning(
|
||||
"'bumper' section not found in context configuration"
|
||||
)
|
||||
except Exception as e:
|
||||
mqttserverlog.exception("{}".format(e))
|
||||
|
||||
async def authenticate(self, *args, **kwargs):
|
||||
async def authenticate(self, *, session=None, **kwargs):
|
||||
authenticated = False
|
||||
|
||||
|
||||
try:
|
||||
session = kwargs.get("session", None)
|
||||
username = session.username
|
||||
password = session.password
|
||||
client_id = session.client_id
|
||||
|
|
@ -295,14 +298,12 @@ class BumperMQTTServer_Plugin:
|
|||
|
||||
except Exception as e:
|
||||
mqttserverlog.exception(
|
||||
"Session: {} - {}".format((kwargs.get("session", None)), e)
|
||||
"Session: {} - {}".format(session, e)
|
||||
)
|
||||
authenticated = False
|
||||
|
||||
# Check for allow anonymous
|
||||
allow_anonymous = self.auth_config.get(
|
||||
"allow-anonymous", True
|
||||
)
|
||||
allow_anonymous = self._allow_anonymous
|
||||
if allow_anonymous and not authenticated: # If anonymous auth is allowed and it isn't already authenticated
|
||||
authenticated = True
|
||||
self.context.logger.debug(f"Anonymous Authentication Success: config allows anonymous - Username: {username}")
|
||||
|
|
@ -311,7 +312,7 @@ class BumperMQTTServer_Plugin:
|
|||
return authenticated
|
||||
|
||||
def _read_password_file(self):
|
||||
password_file = self.auth_config.get('password-file', None)
|
||||
password_file = self._password_file
|
||||
if password_file:
|
||||
try:
|
||||
with open(password_file) as f:
|
||||
|
|
@ -327,7 +328,7 @@ class BumperMQTTServer_Plugin:
|
|||
except FileNotFoundError:
|
||||
self.context.logger.warning(f"Password file {password_file} not found")
|
||||
|
||||
async def on_broker_client_connected(self, client_id):
|
||||
async def on_broker_client_connected(self, client_id, **kwargs):
|
||||
|
||||
didsplit = str(client_id).split("@")
|
||||
|
||||
|
|
@ -405,7 +406,7 @@ class BumperMQTTServer_Plugin:
|
|||
bumper.mqtt_helperbot.command_responses.remove(msg)
|
||||
|
||||
|
||||
async def on_broker_client_disconnected(self, client_id):
|
||||
async def on_broker_client_disconnected(self, client_id, **kwargs):
|
||||
|
||||
didsplit = str(client_id).split("@")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue