mirror of
https://github.com/nikdoof/aaisp2mqtt.git
synced 2025-12-17 04:39:22 +00:00
Added support for HA discovery
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
FROM python:3.8-alpine
|
FROM python:3.8-alpine
|
||||||
|
|
||||||
COPY requirements.txt /app/
|
COPY requirements.txt /app/
|
||||||
COPY aaisp-to-mqtt.py /app/
|
COPY aaisp2mqtt.py /app/
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
CMD ["python", "/app/aaisp-to-mqtt.py"]
|
CMD ["python", "/app/aaisp2mqtt.py"]
|
||||||
|
|||||||
1
LICENSE
1
LICENSE
@@ -2,6 +2,7 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Nat Morris
|
Copyright (c) 2016 Nat Morris
|
||||||
|
Copyright (c) 2020 Andrew Williams
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
43
README.md
43
README.md
@@ -1,12 +1,13 @@
|
|||||||
# AAISP to MQTT Service #
|
# AAISP to MQTT Service #
|
||||||
|
|
||||||
A script to publish [Andrews & Arnold / AAISP](http://aa.net.uk) broadband quota and sync rates to [MQTT](http://mqtt.org/).
|
A script to publish [Andrews & Arnold / AAISP](http://aa.net.uk) broadband quota and sync rates to [MQTT](http://mqtt.org/). It uses version 2 of AAISPs [CHAOS](https://support.aa.net.uk/CHAOS) API. Useful for integrating and displaying AAISP line properties in home automation applications, such as [Home Assistant](https://home-assistant.io/) or [openHAB](http://www.openhab.org/).
|
||||||
|
|
||||||
It uses version 2 of AAISPs [CHAOS](https://support.aa.net.uk/CHAOS) API.
|
This is a fork of the original [aaisp2mqtt project](https://github.com/natm/aaisp-to-mqtt) by [natm](http://github.com/natm) with the aim to increase integration with Home Assistant and fixing a few minor issues.
|
||||||
|
|
||||||
Useful for integrating and displaying AAISP line properties in home automation applications, such as [Home Assistant](https://home-assistant.io/) or [openHAB](http://www.openhab.org/).
|
## Features ##
|
||||||
|
|
||||||
|
* Home Assistant auto discovery
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Use cases ##
|
## Use cases ##
|
||||||
|
|
||||||
@@ -15,19 +16,13 @@ Useful for integrating and displaying AAISP line properties in home automation a
|
|||||||
* Flashing a light in the office when the downstream sync rate drops
|
* Flashing a light in the office when the downstream sync rate drops
|
||||||
* Sending line info to [Crouton](https://github.com/edfungus/Crouton)
|
* Sending line info to [Crouton](https://github.com/edfungus/Crouton)
|
||||||
|
|
||||||
Example showing lines in Home Assistant...
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Configuration ##
|
## Configuration ##
|
||||||
|
|
||||||
Create a config file, for example in /etc/aaisp-mqtt.conf, minimal viable with no MQTT authentication:
|
Create a config file, for example in /etc/aaisp-mqtt.conf, minimal viable with no MQTT authentication:
|
||||||
|
|
||||||
```
|
```
|
||||||
[aaisp]
|
[aaisp]
|
||||||
username = aa@1
|
username = aa000@x.a
|
||||||
password = LongAccountPassword
|
password = LongAccountPassword
|
||||||
|
|
||||||
[mqtt]
|
[mqtt]
|
||||||
@@ -40,7 +35,7 @@ You can also optionally specify MQTT username and password:
|
|||||||
|
|
||||||
```
|
```
|
||||||
[aaisp]
|
[aaisp]
|
||||||
username = aa@1
|
username = aa000@x.a
|
||||||
password = LongAccountPassword
|
password = LongAccountPassword
|
||||||
|
|
||||||
[mqtt]
|
[mqtt]
|
||||||
@@ -60,21 +55,21 @@ $ pip install -r requirements.txt
|
|||||||
Run the service:
|
Run the service:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ aaisp-to-mqtt.py /etc/aaisp-mqtt.conf
|
$ aaisp2mqtt.py /etc/aaisp-mqtt.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
It will display debug output similar to:
|
It will display debug output similar to:
|
||||||
|
|
||||||
```
|
```
|
||||||
INFO [2016-11-16 01:24:07,069] Connecting to AAISP CHAOSv2 endpoint
|
INFO [2020-05-16 14:49:05,142] Connecting to AAISP CHAOSv2 endpoint as xx000@x.a
|
||||||
INFO [2016-11-16 01:24:07,338] Got 3 circuits
|
INFO [2020-05-16 14:49:06,002] Got 1 circuits
|
||||||
INFO [2016-11-16 01:24:07,338] * Lines: 32891, 37835, 37964
|
INFO [2020-05-16 14:49:06,003] * Lines: 41429
|
||||||
INFO [2016-11-16 01:24:07,338] * Logins: gb12@a.1, el6@a.1, el6@a.2
|
INFO [2020-05-16 14:49:06,004] * Logins: xx000@x.0
|
||||||
INFO [2016-11-16 01:24:07,339] Connecting to MQTT broker mqtt.gorras.hw.esgob.com:1883
|
INFO [2020-05-16 14:49:06,005] Connecting to MQTT broker 127.0.0.1:1883
|
||||||
INFO [2016-11-16 01:24:07,345] Connected OK to MQTT
|
INFO [2020-05-16 14:49:06,016] Connected to MQTT Server 127.0.0.1
|
||||||
INFO [2016-11-16 01:24:07,346] Published version and index messages
|
INFO [2020-05-16 14:49:06,023] Published version and index messages
|
||||||
INFO [2016-11-16 01:24:07,350] Published details for 3 circuits
|
INFO [2020-05-16 14:49:06,031] Published details for 1 circuits
|
||||||
INFO [2016-11-16 01:24:07,350] Disconnecting from MQTT
|
INFO [2020-05-16 14:49:06,033] Disconnecting from MQTT
|
||||||
```
|
```
|
||||||
|
|
||||||
Schedule the script via a crontab to run every hour or 30 minutes.
|
Schedule the script via a crontab to run every hour or 30 minutes.
|
||||||
@@ -158,6 +153,8 @@ Or you can pass the configuration values as environment variables:
|
|||||||
* MQTT_USERNAME
|
* MQTT_USERNAME
|
||||||
* MQTT_PASSWORD
|
* MQTT_PASSWORD
|
||||||
* MQTT_TOPIC_PREFIX
|
* MQTT_TOPIC_PREFIX
|
||||||
|
* HOMEASSISTANT_ENABLED
|
||||||
|
* HOMEASSISTANT_DISCOVERY_PREFIX
|
||||||
|
|
||||||
## Setup ##
|
## Setup ##
|
||||||
|
|
||||||
@@ -174,4 +171,4 @@ MIT
|
|||||||
* Make your changes
|
* Make your changes
|
||||||
* Open a pull request back from your branch to master in this repo
|
* Open a pull request back from your branch to master in this repo
|
||||||
|
|
||||||
Found a bug? open an [issue](https://github.com/natm/aaisp-to-mqtt/issues).
|
Found a bug? open an [issue](https://github.com/nikdoof/aaisp2mqtt/issues).
|
||||||
|
|||||||
165
aaisp-to-mqtt.py
165
aaisp-to-mqtt.py
@@ -1,165 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import configparser
|
|
||||||
import paho.mqtt.client as mqtt
|
|
||||||
import humanfriendly
|
|
||||||
import requests
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
VERSION = '0.2.3'
|
|
||||||
|
|
||||||
AAISP_INFO_URL = 'https://chaos2.aa.net.uk/broadband/info'
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
logging.basicConfig(level=logging.INFO,
|
|
||||||
format='%(levelname)8s [%(asctime)s] %(message)s')
|
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
cfgfile = sys.argv[1]
|
|
||||||
# load the config
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(cfgfile)
|
|
||||||
|
|
||||||
# check it has the correct sections
|
|
||||||
for section in ['aaisp', 'mqtt']:
|
|
||||||
if section not in config.sections():
|
|
||||||
LOG.fatal('%s section not found in config file %s',
|
|
||||||
section, cfgfile)
|
|
||||||
|
|
||||||
aaisp_username = config.get('aaisp', 'username')
|
|
||||||
aaisp_password = config.get('aaisp', 'password')
|
|
||||||
mqtt_broker = config.get('mqtt', 'broker')
|
|
||||||
mqtt_port = int(config.get('mqtt', 'port', fallback='1883'))
|
|
||||||
mqtt_username = config.get('mqtt', 'username', fallback=None)
|
|
||||||
mqtt_password = config.get('mqtt', 'password', fallback=None)
|
|
||||||
mqtt_topic_prefix = config.get('mqtt', 'topic_prefix', fallback='aaisp')
|
|
||||||
else:
|
|
||||||
# Use the environment
|
|
||||||
aaisp_username = os.environ.get('AAISP_USERNAME')
|
|
||||||
aaisp_password = os.environ.get('AAISP_PASSWORD')
|
|
||||||
mqtt_broker = os.environ.get('MQTT_BROKER') or 'localhost'
|
|
||||||
mqtt_port = int(os.environ.get('MQTT_PORT') or '1883')
|
|
||||||
mqtt_username = os.environ.get('MQTT_USERNAME')
|
|
||||||
mqtt_password = os.environ.get('MQTT_PASSWORD')
|
|
||||||
mqtt_topic_prefix = os.environ.get('MQTT_TOPIC_PREFIX') or 'aaisp'
|
|
||||||
|
|
||||||
if aaisp_username is None or aaisp_password is None:
|
|
||||||
LOG.fatal('Username or Password missing for AAISP')
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# attempt to get details from aaisp
|
|
||||||
LOG.info('Connecting to AAISP CHAOSv2 endpoint as %s/%s', aaisp_username, '*' * len(aaisp_password))
|
|
||||||
response = requests.get(AAISP_INFO_URL, params={
|
|
||||||
'control_login': aaisp_username.encode('ascii'),
|
|
||||||
'control_password': aaisp_password.encode('ascii')
|
|
||||||
})
|
|
||||||
if not response.status_code == requests.codes.ok:
|
|
||||||
LOG.error('Error connecting to AAISP CHAOSv2 endpoint: %s' % response.body)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
data = response.json()
|
|
||||||
|
|
||||||
# Check for response errors
|
|
||||||
if 'info' not in data:
|
|
||||||
if 'error' in data:
|
|
||||||
LOG.fatal('Error encounted: %s' % data['error'])
|
|
||||||
else:
|
|
||||||
LOG.fatal('info section not found in AAISP CHAOSv2 response')
|
|
||||||
return 1
|
|
||||||
|
|
||||||
circuits = data['info']
|
|
||||||
LOG.info('Got %s circuits', len(circuits))
|
|
||||||
if len(circuits) == 0:
|
|
||||||
LOG.fatal('No circuits returned from AAISP CHAOSv2')
|
|
||||||
|
|
||||||
# work out unique line IDs and logins
|
|
||||||
logins = set(c['login'] for c in circuits)
|
|
||||||
lines = set(c['ID'] for c in circuits)
|
|
||||||
LOG.info('* Lines: %s', ', '.join(lines))
|
|
||||||
LOG.info('* Logins: %s', ', '.join(logins))
|
|
||||||
|
|
||||||
# connect to the broker
|
|
||||||
LOG.info('Connecting to MQTT broker %s:%s', mqtt_broker, mqtt_port)
|
|
||||||
client = mqtt.Client()
|
|
||||||
client.max_inflight_messages_set(100)
|
|
||||||
|
|
||||||
# do auth?
|
|
||||||
if mqtt_username is not None and mqtt_password is not None:
|
|
||||||
client.username_pw_set(mqtt_username, mqtt_password)
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.connect(mqtt_broker, mqtt_port, 60)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception('Error connecting to MQTT')
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
LOG.info('Connected to MQTT Server %s', mqtt_broker)
|
|
||||||
|
|
||||||
# version and indexes
|
|
||||||
publish(client=client, topic='%s/$version' %
|
|
||||||
(mqtt_topic_prefix), payload=VERSION)
|
|
||||||
publish(client=client, topic='%s/$lines' %
|
|
||||||
(mqtt_topic_prefix), payload=','.join(lines))
|
|
||||||
publish(client=client, topic='%s/$logins' %
|
|
||||||
(mqtt_topic_prefix), payload=','.join(logins))
|
|
||||||
LOG.info('Published version and index messages')
|
|
||||||
|
|
||||||
# publish per circuit
|
|
||||||
for circuit in circuits:
|
|
||||||
publish_per_circuit(client=client, circuit=circuit,
|
|
||||||
mqtt_topic_prefix=mqtt_topic_prefix)
|
|
||||||
LOG.info('Published details for %s circuits', len(circuits))
|
|
||||||
|
|
||||||
# disconnect
|
|
||||||
LOG.info('Disconnecting from MQTT')
|
|
||||||
client.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
def publish_per_circuit(client, circuit, mqtt_topic_prefix):
|
|
||||||
quota_remaining = int(circuit['quota_remaining'])
|
|
||||||
quota_remaining_gb = quota_remaining / 1000000000
|
|
||||||
quota_monthly = int(circuit['quota_monthly'])
|
|
||||||
quota_monthly_gb = quota_monthly / 1000000000
|
|
||||||
up = float(circuit['rx_rate'])
|
|
||||||
up_mb = round(up / 1000000, 2)
|
|
||||||
down = float(circuit['tx_rate'])
|
|
||||||
down_mb = round(down / 1000000, 2)
|
|
||||||
|
|
||||||
# line_prefix = '%s/line/%s' % (mqtt_topic_prefix, circuit['ID'])
|
|
||||||
login_prefix = '%s/login/%s' % (mqtt_topic_prefix, circuit['login'])
|
|
||||||
for prefix in [login_prefix]: # , line_prefix]:
|
|
||||||
for metric in [
|
|
||||||
('quota/remaining', quota_remaining),
|
|
||||||
('quota/remaining/gb', quota_remaining_gb),
|
|
||||||
('quota/remaining/human', humanfriendly.format_size(quota_remaining)),
|
|
||||||
('quota/monthly', quota_monthly),
|
|
||||||
('quota/monthly/gb', quota_monthly_gb),
|
|
||||||
('quota/monthly/human', humanfriendly.format_size(quota_monthly)),
|
|
||||||
('syncrate/up', up),
|
|
||||||
('syncrate/up/mb', up_mb),
|
|
||||||
('syncrate/up/human', humanfriendly.format_size(up)),
|
|
||||||
('syncrate/down', down),
|
|
||||||
('syncrate/down/mb', down_mb),
|
|
||||||
('syncrate/down/human', humanfriendly.format_size(down)),
|
|
||||||
('postcode', str(circuit['postcode'].strip()))
|
|
||||||
]:
|
|
||||||
topic = '%s/%s' % (prefix, metric[0])
|
|
||||||
publish(client=client, topic=topic, payload=metric[1])
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def publish(client, topic, payload):
|
|
||||||
result = client.publish(topic=topic, payload=payload, qos=1)
|
|
||||||
if result[0] != 0:
|
|
||||||
LOG.fail('MQTT publish failure: %s %s', topic, payload)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
12
aaisp.cfg-example
Normal file
12
aaisp.cfg-example
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[aaisp]
|
||||||
|
username = xx000@x
|
||||||
|
password = PasswordPassword
|
||||||
|
|
||||||
|
[mqtt]
|
||||||
|
broker = localhost
|
||||||
|
port = 1883
|
||||||
|
topic_prefix = aaisp
|
||||||
|
|
||||||
|
[homeassistant]
|
||||||
|
enabled = false
|
||||||
|
discovery_prefix = homeassistant
|
||||||
203
aaisp2mqtt.py
Normal file
203
aaisp2mqtt.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import humanfriendly
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
VERSION = '0.3.0'
|
||||||
|
|
||||||
|
AAISP_INFO_URL = 'https://chaos2.aa.net.uk/broadband/info'
|
||||||
|
|
||||||
|
|
||||||
|
def b_to_gb(value):
|
||||||
|
"""Bytes to Gibibytes"""
|
||||||
|
return round(int(value) / (1000**3), 3)
|
||||||
|
|
||||||
|
|
||||||
|
def bps_to_mbps(value):
|
||||||
|
"""Bits per second to Mbit/sec"""
|
||||||
|
return round(int(value) / (1000**2), 3)
|
||||||
|
|
||||||
|
def to_human(value):
|
||||||
|
"""Human readable value"""
|
||||||
|
return humanfriendly.format_size(int(value))
|
||||||
|
|
||||||
|
|
||||||
|
# Name, Topic, Key, Formatter, HA Unit Type
|
||||||
|
VALUES_MAP = [
|
||||||
|
('quota_remaining', 'quota/remaining', 'quota_remaining', int, 'B', 'mdi:gauge'),
|
||||||
|
('quota_remaining_gb', 'quota/remaining/gb', 'quota_remaining', b_to_gb, 'GB', 'mdi:gauge'),
|
||||||
|
('quota_remaining_human', 'quota/remaining/human', 'quota_remaining', to_human, '', 'mdi:gauge'),
|
||||||
|
('quota_monthly', 'quota/monthly', 'quota_monthly', int, 'B', 'mdi:gauge'),
|
||||||
|
('quota_monthly_gb', 'quota/monthly/gb', 'quota_monthly', b_to_gb, 'GB', 'mdi:gauge'),
|
||||||
|
('quota_monthly_human', 'quota/monthly/human', 'quota_monthly', to_human, '', 'mdi:gauge'),
|
||||||
|
('syncrate_up', 'syncrate/up', 'rx_rate', float, 'bit/s', 'mdi:speedometer'),
|
||||||
|
('syncrate_up_mbps', 'syncrate/up/mb', 'rx_rate', bps_to_mbps, 'Mbit/s', 'mdi:speedometer'),
|
||||||
|
('syncrate_up_human', 'syncrate/up/human', 'rx_rate', to_human, '', 'mdi:speedometer'),
|
||||||
|
('syncrate_down', 'syncrate/down', 'tx_rate', float, 'bit/s', 'mdi:speedometer'),
|
||||||
|
('syncrate_down_mbps', 'syncrate/down/mb', 'tx_rate', bps_to_mbps, 'Mbit/s', 'mdi:speedometer'),
|
||||||
|
('syncrate_down_human', 'syncrate/down/human', 'tx_rate', to_human, '', 'mdi:speedometer'),
|
||||||
|
('postcode', 'postcode', 'postcode', str, '', 'mdi:tag-text'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logging.basicConfig(level=logging.INFO,
|
||||||
|
format='%(levelname)8s [%(asctime)s] %(message)s')
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
filename = os.path.abspath(os.path.expandvars(sys.argv[1]))
|
||||||
|
# load the config
|
||||||
|
if os.path.exists(filename):
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(filename)
|
||||||
|
else:
|
||||||
|
LOG.fatal('Configuration file %s does not exist', filename)
|
||||||
|
|
||||||
|
# check it has the correct sections
|
||||||
|
diff = set(['aaisp', 'mqtt']) - set(config.sections())
|
||||||
|
if len(diff) > 0:
|
||||||
|
LOG.fatal('Sections are missing from the configuration file: %s', ','.join(diff))
|
||||||
|
|
||||||
|
aaisp_username = config.get('aaisp', 'username')
|
||||||
|
aaisp_password = config.get('aaisp', 'password')
|
||||||
|
mqtt_broker = config.get('mqtt', 'broker')
|
||||||
|
mqtt_port = int(config.get('mqtt', 'port', fallback='1883'))
|
||||||
|
mqtt_username = config.get('mqtt', 'username', fallback=None)
|
||||||
|
mqtt_password = config.get('mqtt', 'password', fallback=None)
|
||||||
|
mqtt_topic_prefix = config.get('mqtt', 'topic_prefix', fallback='aaisp')
|
||||||
|
homeassistant_enabled = config.get('homeassistant', 'enabled', fallback='false') == 'true'
|
||||||
|
homeassistant_discovery_prefix = config.get('homeassistant', 'discovery_prefix', fallback='homeassistant')
|
||||||
|
else:
|
||||||
|
# Use the environment
|
||||||
|
aaisp_username = os.environ.get('AAISP_USERNAME')
|
||||||
|
aaisp_password = os.environ.get('AAISP_PASSWORD')
|
||||||
|
mqtt_broker = os.environ.get('MQTT_BROKER') or 'localhost'
|
||||||
|
mqtt_port = int(os.environ.get('MQTT_PORT') or '1883')
|
||||||
|
mqtt_username = os.environ.get('MQTT_USERNAME')
|
||||||
|
mqtt_password = os.environ.get('MQTT_PASSWORD')
|
||||||
|
mqtt_topic_prefix = os.environ.get('MQTT_TOPIC_PREFIX') or 'aaisp'
|
||||||
|
homeassistant_enabled = (os.environ.get('HOMEASSISTANT_ENABLED') or 'false') == 'true'
|
||||||
|
homeassistant_discovery_prefix = os.environ.get('HOMEASSISTANT_DISCOVERY_PREFIX') or 'homeassistant'
|
||||||
|
|
||||||
|
if aaisp_username is None or aaisp_password is None:
|
||||||
|
LOG.fatal('Username or Password missing for AAISP')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# attempt to get details from aaisp
|
||||||
|
LOG.info('Connecting to AAISP CHAOSv2 endpoint as %s', aaisp_username)
|
||||||
|
response = requests.get(AAISP_INFO_URL, params={
|
||||||
|
'control_login': aaisp_username,
|
||||||
|
'control_password': aaisp_password
|
||||||
|
})
|
||||||
|
if not response.status_code == requests.codes.ok:
|
||||||
|
LOG.error('Error connecting to AAISP CHAOSv2 endpoint: %s' % response.body)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# Check for response errors
|
||||||
|
if 'info' not in data:
|
||||||
|
if 'error' in data:
|
||||||
|
LOG.fatal('Error encounted: %s' % data['error'])
|
||||||
|
else:
|
||||||
|
LOG.fatal('info section not found in AAISP CHAOSv2 response')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
circuits = data['info']
|
||||||
|
LOG.info('Got %s circuits', len(circuits))
|
||||||
|
if len(circuits) == 0:
|
||||||
|
LOG.fatal('No circuits returned from AAISP CHAOSv2')
|
||||||
|
|
||||||
|
# work out unique line IDs and logins
|
||||||
|
logins = set(c['login'] for c in circuits)
|
||||||
|
lines = set(c['ID'] for c in circuits)
|
||||||
|
LOG.info('* Lines: %s', ', '.join(lines))
|
||||||
|
LOG.info('* Logins: %s', ', '.join(logins))
|
||||||
|
|
||||||
|
# connect to the broker
|
||||||
|
LOG.info('Connecting to MQTT broker %s:%s', mqtt_broker, mqtt_port)
|
||||||
|
client = mqtt.Client()
|
||||||
|
client.max_inflight_messages_set(100)
|
||||||
|
|
||||||
|
# do auth?
|
||||||
|
if mqtt_username is not None and mqtt_password is not None:
|
||||||
|
client.username_pw_set(mqtt_username, mqtt_password)
|
||||||
|
|
||||||
|
try:
|
||||||
|
client.connect(mqtt_broker, mqtt_port, 60)
|
||||||
|
except Exception:
|
||||||
|
LOG.exception('Error connecting to MQTT')
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
LOG.info('Connected to MQTT Server %s', mqtt_broker)
|
||||||
|
|
||||||
|
# version and indexes
|
||||||
|
publish(client=client, topic='%s/$version' % (mqtt_topic_prefix), payload=VERSION)
|
||||||
|
publish(client=client, topic='%s/$lines' % (mqtt_topic_prefix), payload=','.join(lines))
|
||||||
|
publish(client=client, topic='%s/$logins' % (mqtt_topic_prefix), payload=','.join(logins))
|
||||||
|
publish(client=client, topic='%s/last_update' % (mqtt_topic_prefix), payload=datetime.now().timestamp())
|
||||||
|
LOG.info('Published version and index messages')
|
||||||
|
|
||||||
|
# publish per circuit
|
||||||
|
for circuit in circuits:
|
||||||
|
# If homeassistant is enabled, publish the sensor configs
|
||||||
|
if homeassistant_enabled:
|
||||||
|
LOG.debug('Publishing Homeassistant configuration.')
|
||||||
|
publish_circuit_config(client, circuit, mqtt_topic_prefix, homeassistant_discovery_prefix)
|
||||||
|
|
||||||
|
# Publish the states
|
||||||
|
publish_circuit_state(client, circuit, mqtt_topic_prefix)
|
||||||
|
LOG.info('Published details for %s circuits', len(circuits))
|
||||||
|
|
||||||
|
# disconnect
|
||||||
|
LOG.info('Disconnecting from MQTT')
|
||||||
|
client.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
def publish_circuit_state(client, circuit, mqtt_topic_prefix):
|
||||||
|
prefix = '%s/login/%s' % (mqtt_topic_prefix, circuit['login'])
|
||||||
|
for _, topic, key, formatter, _, _ in VALUES_MAP:
|
||||||
|
topic = '%s/%s' % (prefix, topic)
|
||||||
|
publish(client=client, topic=topic, payload=formatter(circuit[key]))
|
||||||
|
|
||||||
|
|
||||||
|
def publish_circuit_config(client, circuit, mqtt_topic_prefix, mqtt_discovery_prefix):
|
||||||
|
for name, topic, _, _, unit, icon in VALUES_MAP:
|
||||||
|
login = circuit['login'].replace('@', '_').replace('.', '_')
|
||||||
|
config_topic = '%s/sensor/%s/%s/config' % (mqtt_discovery_prefix, login, name)
|
||||||
|
unique_id = '%s_%s' % (login, name)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'name': '%s %s' % (circuit['login'], name),
|
||||||
|
'icon': icon,
|
||||||
|
'state_topic': '%s/login/%s/%s' % (mqtt_topic_prefix, circuit['login'], topic),
|
||||||
|
'unique_id': unique_id,
|
||||||
|
'unit_of_measurement': unit,
|
||||||
|
'device': {
|
||||||
|
'identifiers': circuit['login'],
|
||||||
|
'name': 'AAISP Circuit %s (%s)' % (circuit['login'], circuit['postcode']),
|
||||||
|
'sw_version': VERSION,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publish(client, config_topic, payload=json.dumps(data), retain=True)
|
||||||
|
|
||||||
|
|
||||||
|
def publish(client, topic, payload, retain=False):
|
||||||
|
result = client.publish(topic=topic, payload=payload, qos=0, retain=retain)
|
||||||
|
if result[0] != 0:
|
||||||
|
LOG.fail('MQTT publish failure: %s %s', topic, payload)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 71 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB |
@@ -1,4 +1,3 @@
|
|||||||
paho-mqtt>=1.2
|
paho-mqtt>=1.2
|
||||||
configparser>=3.5.0
|
|
||||||
humanfriendly>=2.1
|
humanfriendly>=2.1
|
||||||
requests>=2.23.0
|
requests>=2.23.0
|
||||||
|
|||||||
35
setup.py
Normal file
35
setup.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
with open("README.md", "r") as fh:
|
||||||
|
long_description = fh.read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='aaisp2mqtt',
|
||||||
|
version='0.3.0',
|
||||||
|
description='A script to publish Andrews & Arnold / AAISP broadband quota and sync rates to MQTT',
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
|
license='MIT',
|
||||||
|
author='Nat Morris, Andrew Williams',
|
||||||
|
author_email='nat@nuqe.net, andy@tensixtyone.com',
|
||||||
|
url='https://github.com/nikdoof/aaisp2mqtt',
|
||||||
|
packages=find_packages(),
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
],
|
||||||
|
python_requires='>=3.7',
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'aaisp2mqtt = aaisp2mqtt:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
install_requires=[
|
||||||
|
'paho-mqtt>=1.2',
|
||||||
|
'humanfriendly>=2.1',
|
||||||
|
'requests>=2.23.0',
|
||||||
|
]
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user