From bcf6a5f0df5154429870015745e22ad12c1b2cbd Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Tue, 18 May 2021 20:03:06 +0300 Subject: Add MQTT support and bump to v1.3.0 (#12) * Initial MQTT branch * Cont * Added info to README. Added MQTT filter. * Added MQTT filter. * Fixed using localized string for filter * Added support to filter by area name Fixed compare case insensitive Updated README * Revert change * Fix error on missing mqtt init * Reorder params * Cleanup MQTT implementation * Update MQTT README * Bump to v1.3.0 Co-authored-by: Adi Miller --- README.md | 24 +++++++++++++++++ alarmpy/alarmpy.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++++----- setup.py | 12 +++++++-- 3 files changed, 107 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 979ef1d..02e178d 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,33 @@ Options: --quiet Print only active alarms --desktop-notifications Create push notifications on your desktop notification center (currently only in Mac OS) + --mqtt-server TEXT Hostname / IP of MQTT server (optional) + --mqtt-client-id TEXT MQTT client identifier + --mqtt-port INTEGER Port for MQTT server + --mqtt-topic TEXT Topic on which to send MQTT messages + --mqtt-filter TEXT Payload value to filter before sending as a + message (semicolon separated) --help Show this message and exit. ``` +### MQTT Notifications + +Integration with an MQTT server provides the ability to send custom MQTT messages for all or some of the alerts that are received. MQTT requires [`paho-mqtt`](https://pypi.org/project/paho-mqtt/) to be installed separately as an optional dependency. + +To enable, specify at least the following parameters via the command line: + +- `mqtt-server` - The MQTT Server hostname or IP, e.g. `localhost` +- `mqtt-topic` - The MQTT topic to which the MQTT message will be sent, e.g. `alarmpy/zone` + +Additional optional parameters for MQTT integration are: + +- `mqtt-client-id` - The ID of the MQTT client used by alarmpy. This will be used to connect to the MQTT server. Default: `alarmPyClient`. This only needs to be change in case you plan to have more than one instance of alarmpy running +- `mqtt-port` - The port on which the MQTT server is listening to. Default: `1883` + +#### Filtering + +When MQTT is enabled, all alerts are sent as separate messages on the specified topic. In case there is a desire to include only specific alert, use the `mqtt-filter` parameter to provide a semicolon separated list of substrings enclosed in double quotes. Each alert city and area will be checked against all filters, and only when a match is found, will an MQTT message be sent. For example: `--mqtt-filter "gaza;negev"`. + ## License [GPLv3](LICENSE) diff --git a/alarmpy/alarmpy.py b/alarmpy/alarmpy.py index 4fbe399..248b326 100644 --- a/alarmpy/alarmpy.py +++ b/alarmpy/alarmpy.py @@ -8,6 +8,11 @@ from datetime import datetime from pathlib import Path from time import sleep, time +try: + import paho.mqtt.client as mqtt +except ImportError: + pass + class Alarm: @@ -28,6 +33,11 @@ class Alarm: repeat_alarms=False, quiet=False, desktop_notifications=False, + mqtt_server="", + mqtt_client_id="alarmPyClient", + mqtt_port=1883, + mqtt_topic="", + mqtt_filter=None, ): self.language = language self.polling_delay = polling_delay @@ -35,23 +45,50 @@ class Alarm: self.alarm_id = alarm_id self.repeat_alarms = repeat_alarms self.quiet = quiet - - if desktop_notifications and not os.path.exists("/usr/bin/osascript"): - self.output_error( - "Desktop notifications are currently only available for MacOS" - ) - desktop_notifications = False self.desktop_notifications = desktop_notifications + self.mqtt_server = mqtt_server + self.mqtt_client_id = mqtt_client_id + self.mqtt_port = mqtt_port + self.mqtt_topic = mqtt_topic + self.mqtt_filter = mqtt_filter + self.current_alarms = [] self.last_routine_output = 0 self.session = self.init_session() self.labels = self.load_labels() + self.init_desktop_notifications() + self.init_mqtt() + def init_session(self): return requests.Session() + def init_desktop_notifications(self): + if self.desktop_notifications and not os.path.exists("/usr/bin/osascript"): + self.output_error( + "Desktop notifications are currently only available for MacOS" + ) + self.desktop_notifications = False + + def init_mqtt(self): + try: + self.mqtt = mqtt.Client(self.mqtt_client_id) + except NameError: + self.mqtt = None + + self.filters = None + if self.mqtt_server and self.mqtt: + if not self.mqtt: + self.output_error( + "MQTT support cannot be instantiated without the paho-mqtt library installed" + ) + self.mqtt.connect(self.mqtt_server, self.mqtt_port) + self.mqtt.loop_start() + if self.mqtt_filter: + self.filters = self.mqtt_filter.lower().split(";") + def load_labels(self): DATA_DIR = Path(__file__).parent / "data" with open(DATA_DIR / "labels.json", "r") as f: @@ -82,6 +119,7 @@ class Alarm: # empty content means no alarms return [], None + data = {} # To avoid warning in KeyError try: data = res.json() alarm_id = data["id"] @@ -101,6 +139,7 @@ class Alarm: def update_alarm(self, cities, alarm_id): if self.repeat_alarms or set(cities) != set(self.current_alarms): self.output_alarms(cities, alarm_id) + self.notify_alarms(cities) self.current_alarms = cities def update_routine(self): @@ -142,6 +181,21 @@ class Alarm: if self.alarm_id: click.secho(f"({alarm_id})") + def notify_alarms(self, cities): + if self.mqtt_server and self.mqtt_topic: + for city in cities: + labels = self.labels.get(city, {}) + area = labels.get(f"areaname_{self.language}", "") + label = labels.get(f"label_{self.language}", city) + if self.filters is None or self.check_filter(label, area): + self.mqtt.publish(self.mqtt_topic, label) + + def check_filter(self, city, area): + for flt in self.filters: + if flt in city.lower() or flt in area.lower(): + return True + return False + def group_areas_and_localize(self, cities): res = defaultdict(list) for city in cities: @@ -173,6 +227,19 @@ class Alarm: @click.option("--alarm-id", is_flag=True, help="Print alarm IDs") @click.option("--repeat-alarms", is_flag=True, help="Do not suppress ongoing alarms") @click.option("--quiet", is_flag=True, help="Print only active alarms") +@click.option( + "--mqtt-server", default=None, help="Hostname / IP of MQTT server (optional)" +) +@click.option( + "--mqtt-client-id", default="alarmPyClient", help="MQTT client identifier" +) +@click.option("--mqtt-port", default=1883, help="Port for MQTT server") +@click.option("--mqtt-topic", default=None, help="Topic on which to send MQTT messages") +@click.option( + "--mqtt-filter", + default=None, + help="Payload value to filter before sending as a message (semicolon separated)", +) @click.option( "--desktop-notifications", is_flag=True, diff --git a/setup.py b/setup.py index e8952c0..ab74e1e 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh: setuptools.setup( name="alarmpy", - version="1.2.0", + version="1.3.0", author="Yuval Adam", author_email="_@yuv.al", description="Pikud Ha'oref Alarm Tracking", @@ -27,5 +27,13 @@ setuptools.setup( package_data={"alarmpy": ["data/*.json"]}, python_requires=">=3.6", install_requires=["requests", "click"], - entry_points={"console_scripts": ["alarmpy = alarmpy:cli"]}, + extras_require={ + "mqttnotify": ["paho-mqtt"], + }, + entry_points={ + "console_scripts": [ + "alarmpy = alarmpy:cli", + "alarmpynotify = alarmpy:cli [mqttnotify]", + ] + }, ) -- cgit v1.3.1