-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
rewrite against APRS-IS with aprslib
- Loading branch information
1 parent
eafc7cd
commit bc7abb3
Showing
15 changed files
with
383 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
/build/ | ||
/aprs2mqtt/__pycache__/ | ||
*.pyc | ||
/result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,28 @@ | ||
A simple APRS message to MQTT bridge. I consume the messages in Home Assistant and fire out notifications via Telegram. | ||
|
||
* Environment | ||
Configuration is done with these environment variables: | ||
|
||
- MQTT_HOST | ||
- MQTT_PORT | ||
- MQTT_USER | ||
- MQTT_PASSWORD | ||
- MQTT_TOPIC | ||
- APRSFI_KEY: From your [[http://aprs.fi][aprs.fi]] profile | ||
- APRS_CALLSIGNS: Comman separated list of callsigns to monitor | ||
- LOCK_LOCATION: Location to store timestasmps of last message, I use /tmp. | ||
* Usage | ||
#+BEGIN_SRC bash | ||
python -m aprs2mqtt <config> | ||
#+END_SRC | ||
|
||
This repository also supplies a [[https://nixos.wiki/wiki/Flakes][Nix Flake]] which allows configuration with a [[https://nixos.org/][NixOS]] module. | ||
|
||
* Configuration | ||
#+BEGIN_SRC yaml | ||
aprs: | ||
host: euro.aprs2.net | ||
port: 14580 | ||
login: 2E0YML | ||
mqtt: | ||
host: localhost | ||
user: mqtt | ||
pass: mqtt | ||
port: 1883 | ||
consumers: | ||
- filter: t/m | ||
topic: aprs/message | ||
#+END_SRC | ||
|
||
Any values under ~aprs~ or ~mqtt~ can be supplied through environment variables such as ~APRS_HOST~ or ~MQTT_PASS~. | ||
|
||
The consumers section maps [[http://www.aprs-is.net/javAPRSFilter.aspx][user defined filters]]. Matching messages are dispatched to the respective topic. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from .aprs2mqtt import Aprs2MqttService | ||
from .config import Config | ||
import logging | ||
import plac | ||
|
||
def _main(config: ("config", "positional")): | ||
logging.basicConfig(level=logging.DEBUG) | ||
logger = logging.getLogger('aprs2mqtt') | ||
cfg = Config.from_file(config, logger) | ||
Aprs2MqttService.start(cfg, logger=logger) | ||
|
||
def main(): | ||
plac.call(_main) | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import aprslib | ||
import json | ||
import logging | ||
import signal | ||
import sys | ||
from functools import partial | ||
import paho.mqtt.publish as publish | ||
|
||
class Aprs2MqttService: | ||
@staticmethod | ||
def start(config, logger=None): | ||
s = Aprs2MqttService(config, | ||
logger=logger) | ||
s.run() | ||
|
||
def __init__(self, config, logger=None): | ||
self.logger = (logging.Logger('aprs2mqtt') | ||
if logger is None | ||
else logger) | ||
self.config = config | ||
self.consumers = [] | ||
signal.signal(signal.SIGINT, self.stop) | ||
signal.signal(signal.SIGTERM, self.stop) | ||
|
||
def handler(self, msg_filter, packet): | ||
self.logger.debug(f'[{msg_filter}] => {packet}') | ||
publish.single(msg_filter['topic'], | ||
payload=json.dumps(packet), | ||
hostname=self.config.get_mqtt_host(), | ||
port=int(self.config.get_mqtt_port()), | ||
client_id=self.config.get_mqtt_user(), | ||
auth={ | ||
'username': self.config.get_mqtt_user(), | ||
'password': self.config.get_mqtt_pass() | ||
}) | ||
|
||
def run(self): | ||
login = self.config.get_aprs_login() | ||
host = self.config.get_aprs_host() | ||
port = self.config.get_aprs_port() | ||
for f in self.config.get_consumers(): | ||
rule = f['filter'] | ||
topic = f['topic'] | ||
self.logger.info(f'adding consumer {login}@{host}:{port} - {rule} => {topic}') | ||
ais = aprslib.IS(login, host=host, port=port) | ||
ais.connect() | ||
ais.set_filter(rule) | ||
ais.consumer(partial(self.handler, f), raw=False) | ||
self.consumers.append(ais) | ||
|
||
def stop(self, signal, frame): | ||
self.logger.info(f'Caught signal {signal}, exiting') | ||
for ais in self.consumers: | ||
consumer.close() | ||
sys.exit(0); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import os | ||
import logging | ||
import yaml | ||
try: | ||
from yaml import CLoader as Loader, CDumper as Dumper | ||
except ImportError: | ||
from yaml import Loader, Dumper | ||
|
||
class Config: | ||
@staticmethod | ||
def from_file(filename, logger=None): | ||
logger.info(f'Config.from_file({filename})') | ||
with open(filename, 'r') as f: | ||
return Config(yaml.load(f, Loader=Loader), logger) | ||
|
||
def __init__(self, config, logger=None): | ||
self._config = config | ||
self._logger = logger if logger is not None else logging.getLogger() | ||
|
||
def get_config(self, section, name): | ||
self._logger.debug(f'Config.get_config {section}.{name}') | ||
if section in self._config and name in self._config[section]: | ||
return self._config[section][name] | ||
env = f'{section.upper()}_{name.upper()}' | ||
if env in os.environ: | ||
return os.environ[env] | ||
return None | ||
|
||
def get_aprs_host(self): | ||
return self.get_config('aprs', 'host') | ||
|
||
def get_aprs_port(self): | ||
return self.get_config('aprs', 'port') | ||
|
||
def get_aprs_login(self): | ||
return self.get_config('aprs', 'login') | ||
|
||
def get_mqtt_host(self): | ||
return self.get_config('mqtt', 'host') | ||
|
||
def get_mqtt_port(self): | ||
return self.get_config('mqtt', 'port') | ||
|
||
def get_mqtt_user(self): | ||
return self.get_config('mqtt', 'user') | ||
|
||
def get_mqtt_pass(self): | ||
return self.get_config('mqtt', 'pass') | ||
|
||
def get_consumers(self): | ||
self._logger.debug('Config.get_consumers') | ||
if 'consumers' in self._config: | ||
return self._config['consumers'] | ||
return [] |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import json | ||
import unittest | ||
from unittest.mock import patch, Mock, MagicMock, DEFAULT | ||
from .config import Config | ||
|
||
class TestAprs(unittest.TestCase): | ||
|
||
@patch('paho.mqtt.publish.single') | ||
def test_handler(self, mock_publish): | ||
from .aprs2mqtt import Aprs2MqttService | ||
mock_publish = MagicMock() | ||
config = Config({ | ||
'mqtt': { | ||
'host': 'localhost', | ||
'port': 1883, | ||
'user': 'user', | ||
'pass': 'pass' | ||
} | ||
}) | ||
svc = Aprs2MqttService(config) | ||
svc.handler({ 'topic': 'topic' }, | ||
{ 'message': 'ok' }) | ||
mock_publish.asset_called_with( | ||
'topic', | ||
payload=json.dumps({ 'message': 'ok' }), | ||
hostname='localhost', | ||
port=1883, | ||
client_id='user', | ||
auth={ | ||
'username': 'user', | ||
'password': 'pass' | ||
} | ||
) | ||
|
||
@patch('aprslib.IS') | ||
def test_run(self, mock_is): | ||
from .aprs2mqtt import Aprs2MqttService | ||
config = Config({ | ||
'aprs': { | ||
'host': 'localhost', | ||
'port': 1, | ||
'login': 'test' | ||
}, | ||
'consumers': [ | ||
{ 'filter': 'filter', 'topic': 'topic' } | ||
] | ||
}) | ||
svc = Aprs2MqttService(config) | ||
svc.run() | ||
mock_is.assert_called_with('test', host='localhost', port=1) | ||
|
||
@patch.multiple('aprslib.IS', connect=DEFAULT, | ||
set_filter=DEFAULT, consumer=DEFAULT) | ||
def test_connect(self, connect, set_filter, consumer): | ||
from .aprs2mqtt import Aprs2MqttService | ||
config = Config({ | ||
'aprs': { | ||
'host': 'localhost', | ||
'port': 1, | ||
'login': 'test' | ||
}, | ||
'consumers': [ | ||
{ 'filter': 'filter', 'topic': 'topic' } | ||
] | ||
}) | ||
svc = Aprs2MqttService(config) | ||
svc.run() | ||
connect.assert_called() | ||
set_filter.assert_called_with('filter') | ||
consumer.assert_called() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import os | ||
import unittest | ||
from .config import Config | ||
|
||
class TestConfig(unittest.TestCase): | ||
def test_get_config(self): | ||
config = Config({ | ||
'aprs': { | ||
'host': 'localhost' | ||
} | ||
}) | ||
self.assertEqual(config.get_config('aprs', 'host'), 'localhost') | ||
def test_get_config_env(self): | ||
config = Config({}) | ||
os.environ['APRS_HOST'] = 'localhost' | ||
self.assertEqual(config.get_config('aprs', 'host'), 'localhost') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ pkgs ? import <nixpkgs> { } }: | ||
with pkgs.python38Packages; buildPythonPackage { | ||
name = "aprslib"; | ||
src = fetchPypi { | ||
pname = "aprslib"; | ||
version = "0.6.47"; | ||
sha256 = "sha256-V10CX9vpWO5//ilhDfXP5kfLesUf4KdnCnhH1Wuxxgg="; | ||
}; | ||
doCheck = false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,15 @@ | ||
with import <nixpkgs> {}; | ||
pkgs.python37Packages.buildPythonApplication rec { | ||
{ pkgs ? import <nixpkgs> {} }: | ||
let | ||
aprslib = pkgs.callPackage ./aprslib.nix { }; | ||
in pkgs.python38Packages.buildPythonPackage rec { | ||
name = "aprs2mqtt"; | ||
src = ./.; | ||
propagateBuildInputs = with pkgs.python37Packages; [ | ||
paho-mqtt | ||
requests | ||
propagatedBuildInputs = [ | ||
(pkgs.python38.withPackages(ps: with ps; [ | ||
aprslib | ||
paho-mqtt | ||
plac | ||
pyyaml | ||
])) | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
description = "aprs2mqtt"; | ||
outputs = { self, nixpkgs }: | ||
let | ||
pkgs = import nixpkgs { | ||
system = "x86_64-linux"; | ||
overlays = [ self.overlay ]; | ||
}; | ||
in { | ||
overlay = final: prev: { | ||
aprs2mqtt = prev.callPackage ./default.nix { }; | ||
}; | ||
nixosModules.aprs2mqtt = { | ||
imports = [ ./module.nix ]; | ||
nixpkgs.overlays = [ | ||
self.overlay | ||
]; | ||
}; | ||
}; | ||
} |
Oops, something went wrong.