diff --git a/docs/conf.py b/docs/conf.py index 1cdee97..0a9423e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,6 +6,7 @@ extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "myst_parser", -] +] # , "autodoc2" +# autodoc2_packages = ["../heisskleber"] autodoc_typehints = "description" html_theme = "furo" diff --git a/docs/reference.md b/docs/reference.md index 27c6947..20ff891 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -1,8 +1,40 @@ # Reference -## heisskleber +## Network ```{eval-rst} -.. automodule:: heisskleber +.. automodule:: heisskleber.network :members: +.. automodule:: heisskleber.network.mqtt +.. autoclass:: MqttPublisher +.. autoclass:: MqttSubscriber +.. automodule:: heisskleber.network.zmq + :members: +.. autoclass:: ZmqPublisher +.. autoclass:: ZmqSubscriber +``` + +### Broker + +```{eval-rst} +.. automodule:: heisskleber.broker + :members: +``` + +## Config + +### Loading configs +```{eval-rst} +.. automodule:: heisskleber.config + :members: load_config +``` + +### Config types + +Configs are extended dataclasses, which inherit from the BaseConf class. +```{eval-rst} +.. autoclass:: heisskleber.config.BaseConf +.. autoclass:: heisskleber.network.mqtt.config.MqttConf +.. autoclass:: heisskleber.network.zmq.config.ZmqConf +.. autoclass:: heisskleber.network.serial.config.SerialConf ``` diff --git a/heisskleber/broker/__init__.py b/heisskleber/broker/__init__.py new file mode 100644 index 0000000..961abcd --- /dev/null +++ b/heisskleber/broker/__init__.py @@ -0,0 +1,3 @@ +from .msb_broker import msb_broker as start_zmq_broker + +__all__ = ["start_zmq_broker"] diff --git a/heisskleber/broker/msb_broker.py b/heisskleber/broker/msb_broker.py new file mode 100644 index 0000000..db4394e --- /dev/null +++ b/heisskleber/broker/msb_broker.py @@ -0,0 +1,63 @@ +import signal +import sys + +import zmq + +from heisskleber.config import load_config +from heisskleber.network.zmq.config import ZmqConf as BrokerConf + + +def signal_handler(sig, frame): + print("msb_broker.py exit") + sys.exit(0) + + +class BrokerBindingError(Exception): + pass + + +def bind_socket(socket, address, socket_type, verbose=False): + """Bind a ZMQ socket and handle errors.""" + if verbose: + print(f"creating {socket_type} socket") + try: + socket.bind(address) + except Exception as err: + raise BrokerBindingError(f"failed to bind to {socket_type}: {err}") from err + if verbose: + print(f"successfully bound to {socket_type} socket: {address}") + + +def create_proxy(xpub, xsub, verbose=False): + """Create a ZMQ proxy to connect XPUB and XSUB sockets.""" + if verbose: + print("creating proxy") + try: + zmq.proxy(xpub, xsub) + except Exception as err: + raise BrokerBindingError(f"failed to create proxy: {err}") from err + + +def msb_broker(config: BrokerConf) -> None: + """Start a zmq broker. + + Binds to a publisher and subscriber port, allowing many to many connections.""" + ctx = zmq.Context() + + xpub = ctx.socket(zmq.XPUB) + xsub = ctx.socket(zmq.XSUB) + + try: + bind_socket(xpub, config.publisher_address, "publisher", config.verbose) + bind_socket(xsub, config.subscriber_address, "subscriber", config.verbose) + create_proxy(xpub, xsub, config.verbose) + except BrokerBindingError as e: + print(e) + sys.exit(-1) + + +def main() -> None: + """Start a zmq broker, with a user specified configuration.""" + signal.signal(signal.SIGINT, signal_handler) + broker_config = load_config(BrokerConf(), "zmq") + msb_broker(broker_config)