Merge pull request #19 from piranha32/cmdline

Add support for command line arguments
This commit is contained in:
TC² 2024-06-29 08:00:53 -04:00 committed by GitHub
commit be65c2cd40
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 175 additions and 20 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
__pycache__/ __pycache__/
bulletins.db bulletins.db
venv/ venv/
.venv

View file

@ -95,6 +95,36 @@ python server.py
Be sure you've followed the Python virtual environment steps above and activated it before running. Be sure you've followed the Python virtual environment steps above and activated it before running.
## Command line arguments
```
$ python server.py --help
████████╗ ██████╗██████╗ ██████╗ ██████╗ ███████╗
╚══██╔══╝██╔════╝╚════██╗ ██╔══██╗██╔══██╗██╔════╝
██║ ██║ █████╔╝█████╗██████╔╝██████╔╝███████╗
██║ ██║ ██╔═══╝ ╚════╝██╔══██╗██╔══██╗╚════██║
██║ ╚██████╗███████╗ ██████╔╝██████╔╝███████║
╚═╝ ╚═════╝╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝
Meshtastic Version
usage: server.py [-h] [--config CONFIG] [--interface-type {serial,tcp}] [--port PORT] [--host HOST] [--mqtt-topic MQTT_TOPIC]
Meshtastic BBS system
options:
-h, --help show this help message and exit
--config CONFIG, -c CONFIG
System configuration file
--interface-type {serial,tcp}, -i {serial,tcp}
Node interface type
--port PORT, -p PORT Serial port
--host HOST TCP host address
--mqtt-topic MQTT_TOPIC, -t MQTT_TOPIC
MQTT topic to subscribe
```
## Automatically run at boot ## Automatically run at boot
If you would like to have the script automatically run at boot, follow the steps below: If you would like to have the script automatically run at boot, follow the steps below:

View file

@ -15,8 +15,6 @@ from utils import (
update_user_state update_user_state
) )
config, interface_type, hostname, port, bbs_nodes = initialize_config()
def get_node_name(node_id, interface): def get_node_name(node_id, interface):
node_info = interface.nodes.get(node_id) node_info = interface.nodes.get(node_id)

View file

@ -1,12 +1,106 @@
import configparser import configparser
import time import time
from typing import Any
import meshtastic.stream_interface
import meshtastic.serial_interface import meshtastic.serial_interface
import meshtastic.tcp_interface import meshtastic.tcp_interface
import serial.tools.list_ports import serial.tools.list_ports
import argparse
def initialize_config():
def init_cli_parser() -> argparse.Namespace:
"""Function build the CLI parser and parses the arguments.
Returns:
argparse.ArgumentParser: Argparse namespace with processed CLI args
"""
parser = argparse.ArgumentParser(description="Meshtastic BBS system")
parser.add_argument(
"--config", "-c",
action="store",
help="System configuration file",
default=None)
parser.add_argument(
"--interface-type", "-i",
action="store",
choices=['serial', 'tcp'],
help="Node interface type",
default=None)
parser.add_argument(
"--port", "-p",
action="store",
help="Serial port",
default=None)
parser.add_argument(
"--host",
action="store",
help="TCP host address",
default=None)
parser.add_argument(
"--mqtt-topic", '-t',
action="store",
help="MQTT topic to subscribe",
default='meshtastic.receive')
#
# Add extra arguments here
#...
args = parser.parse_args()
return args
def merge_config(system_config:dict[str, Any], args:argparse.Namespace) -> dict[str, Any]:
"""Function merges configuration read from the config file and provided on the CLI.
CLI arguments override values defined in the config file.
system_config argument is mutated by the function.
Args:
system_config (dict[str, Any]): System config dict returned by initialize_config()
args (argparse.Namespace): argparse namespace with parsed CLI args
Returns:
dict[str, Any]: system config dict with merged configurations
"""
if args.interface_type is not None:
system_config['interface_type'] = args.interface_type
if args.port is not None:
system_config['port'] = args.port
if args.host is not None:
system_config['host'] = args.host
return system_config
def initialize_config(config_file:str = None) -> dict[str, Any]:
"""Function reads and parses system configuration file
Returns a dict with the following entries:
config - parsed config file
interface_type - type of the active interface
hostname - host name for TCP interface
port - serial port name for serial interface
bbs_nodes - list of peer nodes to sync with
Args:
config_file (str, optional): Path to config file. Function reads from './config.ini' if this arg is set to None. Defaults to None.
Returns:
dict: dict with system configuration, ad described above
"""
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read('config.ini')
if config_file is None:
config_file = "config.ini"
config.read(config_file)
interface_type = config['interface']['type'] interface_type = config['interface']['type']
hostname = config['interface'].get('hostname', None) hostname = config['interface'].get('hostname', None)
@ -16,14 +110,35 @@ def initialize_config():
if bbs_nodes == ['']: if bbs_nodes == ['']:
bbs_nodes = [] bbs_nodes = []
return config, interface_type, hostname, port, bbs_nodes return {'config':config, 'interface_type': interface_type, 'hostname': hostname, 'port': port, 'bbs_nodes': bbs_nodes, 'mqtt_topic': 'meshtastic.receive'}
def get_interface(interface_type, hostname=None, port=None):
def get_interface(system_config:dict[str, Any]) -> meshtastic.stream_interface.StreamInterface:
"""Function opens and returns an instance meshtastic interface of type specified by the configuration
Function creates and returns an instance of a class inheriting from meshtastic.stream_interface.StreamInterface.
The type of the class depends on the type of the interface specified by the system configuration.
For 'serial' interfaces, function returns an instance of meshtastic.serial_interface.SerialInterface,
and for 'tcp' interface, an instance of meshtastic.tcp_interface.TCPInterface.
Args:
system_config (dict[str, Any]): A dict with system configuration. See description of initialize_config() for details.
Raises:
ValueError: Exception raised in the following cases:
- Type of interface not provided in the system config
- Multiple serial ports present in the system, and no port specified in the configuration
- Serial port interface requested, but no ports found in the system
- Hostname not provided for TCP interface
Returns:
meshtastic.stream_interface.StreamInterface: An instance of StreamInterface
"""
while True: while True:
try: try:
if interface_type == 'serial': if system_config['interface_type'] == 'serial':
if port: if system_config['port']:
return meshtastic.serial_interface.SerialInterface(port) return meshtastic.serial_interface.SerialInterface(system_config['port'])
else: else:
ports = list(serial.tools.list_ports.comports()) ports = list(serial.tools.list_ports.comports())
if len(ports) == 1: if len(ports) == 1:
@ -33,10 +148,10 @@ def get_interface(interface_type, hostname=None, port=None):
raise ValueError(f"Multiple serial ports detected: {port_list}. Specify one with the 'port' argument.") raise ValueError(f"Multiple serial ports detected: {port_list}. Specify one with the 'port' argument.")
else: else:
raise ValueError("No serial ports detected.") raise ValueError("No serial ports detected.")
elif interface_type == 'tcp': elif system_config['interface_type'] == 'tcp':
if not hostname: if not system_config['hostname']:
raise ValueError("Hostname must be specified for TCP interface") raise ValueError("Hostname must be specified for TCP interface")
return meshtastic.tcp_interface.TCPInterface(hostname=hostname) return meshtastic.tcp_interface.TCPInterface(hostname=system_config['hostname'])
else: else:
raise ValueError("Invalid interface type specified in config file") raise ValueError("Invalid interface type specified in config file")
except PermissionError as e: except PermissionError as e:

View file

@ -14,7 +14,7 @@ other BBS servers listed in the config.ini file.
import logging import logging
from config_init import initialize_config, get_interface from config_init import initialize_config, get_interface, init_cli_parser, merge_config
from db_operations import initialize_database from db_operations import initialize_database
from message_processing import on_receive from message_processing import on_receive
from pubsub import pub from pubsub import pub
@ -35,20 +35,32 @@ Meshtastic Version
""" """
print(banner) print(banner)
def main(): def main():
display_banner() display_banner()
config, interface_type, hostname, port, bbs_nodes = initialize_config() # config, interface_type, hostname, port, bbs_nodes = initialize_config()
interface = get_interface(interface_type, hostname, port) args = init_cli_parser()
interface.bbs_nodes = bbs_nodes config_file = None
if args.config is not None:
config_file = args.config
system_config = initialize_config(config_file)
logging.info(f"TC²-BBS is running on {interface_type} interface...") merge_config(system_config, args)
# print(f"{system_config=}")
interface = get_interface(system_config)
interface.bbs_nodes = system_config['bbs_nodes']
logging.info(f"TC²-BBS is running on {system_config['interface_type']} interface...")
initialize_database() initialize_database()
def receive_packet(packet): def receive_packet(packet):
on_receive(packet, interface) on_receive(packet, interface)
pub.subscribe(receive_packet, 'meshtastic.receive') pub.subscribe(receive_packet, system_config['mqtt_topic'])
try: try:
while True: while True: