diff options
-rw-r--r-- | app.py | 17 | ||||
-rw-r--r-- | config.env.example | 10 | ||||
-rw-r--r-- | docker-compose.yml | 15 | ||||
-rw-r--r-- | mikrotik.py | 58 | ||||
-rw-r--r-- | switch-snmp/.dockerignore | 1 | ||||
-rw-r--r-- | switch-snmp/Dockerfile | 16 | ||||
-rw-r--r-- | switch-snmp/entrypoint.sh | 4 | ||||
-rw-r--r-- | switch-snmp/port-names.conf | 4 | ||||
-rw-r--r-- | switch-snmp/requirements.txt | 3 | ||||
-rw-r--r-- | switch-snmp/snmp-omada.py | 100 |
10 files changed, 223 insertions, 5 deletions
@@ -0,0 +1,17 @@ +import database
+import devices
+import flask
+import os
+
+app = flask.Flask(__name__)
+
+@app.route("/")
+def route_index():
+ with database.PowerDatabase(host = devices.HOST) as db:
+ return flask.render_template(
+ "index.html.j2",
+ tasmota_devices = db.get_tasmota_devices()
+ )
+
+if __name__ == "__main__":
+ app.run(host = "0.0.0.0", port = int(os.environ["APP_PORT"]), debug = True)
\ No newline at end of file diff --git a/config.env.example b/config.env.example index 9010652..4878ec2 100644 --- a/config.env.example +++ b/config.env.example @@ -1,10 +1,12 @@ MQTT_USER=eden -MQTT_PASSWD=**************** +MQTT_PASSWD=******************* + +OMADA_SWITCHES=192.168.69.26 DOCKER_INFLUXDB_INIT_MODE=setup DOCKER_INFLUXDB_INIT_USERNAME=eden -DOCKER_INFLUXDB_INIT_PASSWORD=**************** +DOCKER_INFLUXDB_INIT_PASSWORD=******************** DOCKER_INFLUXDB_INIT_ORG=poweredagay DOCKER_INFLUXDB_INIT_BUCKET=edenbucket -DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=************************ -DOCKER_INFLUXDB_DB=power +DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=**************** +DOCKER_INFLUXDB_DB=power
\ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index bede2ad..1c4da89 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,7 +27,7 @@ services: restart: unless-stopped mqtt_client: - image: jwansek/mqtt-client + image: reg.reaweb.uk/mqtt-client build: context: ./mqtt-client dockerfile: Dockerfile @@ -37,6 +37,19 @@ services: - influxdb restart: unless-stopped + snmp_client: + image: reg.reaweb.uk/snmp-client + build: + context: ./switch-snmp + dockerfile: Dockerfile + env_file: + - ./config.env + depends_on: + - influxdb + restart: unless-stopped + volumes: + - ./switch-snmp/port-names.conf:/app/port-names.conf + volumes: mosquitto-data: mosquitto-logs: diff --git a/mikrotik.py b/mikrotik.py new file mode 100644 index 0000000..90dc438 --- /dev/null +++ b/mikrotik.py @@ -0,0 +1,58 @@ +from dataclasses import dataclass, field +import serial +import devices +import time +import os +import re + +@dataclass +class MikroTikSerialDevice: + device: str = os.environ["MIKROTIK_DEVICE"] + user: str = os.environ["MIKROTIK_USER"] + passwd: str = os.environ["MIKROTIK_PASS"] + + def __post_init__(self): + self.interfaces = {} + for i in os.environ["MIKROTIK_INTERFACES"].split(","): + self.interfaces.__setitem__(*i.split(":")) + + def _get_poe_info(self, port): + self.ser = serial.Serial(self.device, 115200, timeout=0.25) + + self._push_serial("") + self._push_serial(self.user) + self._push_serial(self.passwd) + self._push_serial("/interface/ethernet/poe/monitor %s" % port) + time.sleep(0.05) + self.ser.write(bytes("q", 'ISO-8859-1')) + out = self._read() + self.ser.close() + + return self._post_out(out) + + def _push_serial(self, text): + time.sleep(0.05) + self.ser.write(bytes(text + "\r\n", 'ISO-8859-1')) + time.sleep(0.05) + + def _read(self): + return self.ser.readlines() + + def _post_out(self, out): + d = {} + for line in out: + line = line.decode().strip() + if line.startswith("poe"): + d.__setitem__(*line.split(": ")) + + return d + + def get_poes(self): + + print(self.interfaces) + + +if __name__ == "__main__": + mikrotik = MikroTikSerialDevice() + print(mikrotik.get_poes()) + diff --git a/switch-snmp/.dockerignore b/switch-snmp/.dockerignore new file mode 100644 index 0000000..ea6cd72 --- /dev/null +++ b/switch-snmp/.dockerignore @@ -0,0 +1 @@ +port-names.conf
\ No newline at end of file diff --git a/switch-snmp/Dockerfile b/switch-snmp/Dockerfile new file mode 100644 index 0000000..66857fa --- /dev/null +++ b/switch-snmp/Dockerfile @@ -0,0 +1,16 @@ +FROM ubuntu:20.04 +ENV TZ=Europe/London +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone +RUN apt-get update -y +RUN apt-get install -y python3-pip iputils-ping cron wget snmp snmp-mibs-downloader unzip +COPY . /app +WORKDIR /app +RUN wget "https://static.tp-link.com/upload/software/2022/202209/20220915/privateMibs(20220831).zip" +RUN mkdir -p /usr/share/snmp/mibs +RUN unzip -j "privateMibs(20220831).zip" -d /usr/share/snmp/mibs +RUN pip3 install -r requirements.txt +RUN rm "privateMibs(20220831).zip" + +RUN echo "*/1 * * * * root python3 /app/snmp-omada.py > /proc/1/fd/1 2>/proc/1/fd/2" > /etc/crontab +ENTRYPOINT ["bash"] +CMD ["entrypoint.sh"]
\ No newline at end of file diff --git a/switch-snmp/entrypoint.sh b/switch-snmp/entrypoint.sh new file mode 100644 index 0000000..610bf84 --- /dev/null +++ b/switch-snmp/entrypoint.sh @@ -0,0 +1,4 @@ +# https://stackoverflow.com/questions/27771781/how-can-i-access-docker-set-environment-variables-from-a-cron-job/35088810#35088810 +printenv | grep -v "no_proxy" >> /etc/environment + +cron -f
\ No newline at end of file diff --git a/switch-snmp/port-names.conf b/switch-snmp/port-names.conf new file mode 100644 index 0000000..e3f61da --- /dev/null +++ b/switch-snmp/port-names.conf @@ -0,0 +1,4 @@ +1 = Routerbox +5 = PiKVM +6 = EAP225 WiFi +8 = Git Raspberry Pi
\ No newline at end of file diff --git a/switch-snmp/requirements.txt b/switch-snmp/requirements.txt new file mode 100644 index 0000000..e7e63fd --- /dev/null +++ b/switch-snmp/requirements.txt @@ -0,0 +1,3 @@ +python-dotenv +influxdb-client +pandas
\ No newline at end of file diff --git a/switch-snmp/snmp-omada.py b/switch-snmp/snmp-omada.py new file mode 100644 index 0000000..aaa340e --- /dev/null +++ b/switch-snmp/snmp-omada.py @@ -0,0 +1,100 @@ +# wget https://static.tp-link.com/upload/software/2022/202209/20220915/privateMibs(20220831).zip +# cp -v *.mib /home/eden/.snmp/mibs +# sudo apt install snmp +# sudo apt-get install snmp-mibs-downloader + +import subprocess +from dataclasses import dataclass +import dotenv +import os +import pandas + +from influxdb_client import InfluxDBClient, Point, WritePrecision +from influxdb_client.client.write_api import SYNCHRONOUS + +DIVIDE_BY_10_ENDPOINTS = ["tpPoePower", "tpPoeVoltage"] + +PORT_NAMES = dotenv.dotenv_values(os.path.join(os.path.dirname(__file__), "port-names.conf")) +PORT_NAMES = {int(k): v for k, v in PORT_NAMES.items()} + +@dataclass +class SNMPReading: + endpoint: str + port: int + reading: float + + @classmethod + def from_string(cls, str_): + s = str_.split() + if len(s) != 4: + raise Exception("Couldn't parse") + endpoint_and_port, _, type_, reading = s + endpoint, port = endpoint_and_port.split(".") + + if reading.isdigit(): + reading = int(reading) + if endpoint in DIVIDE_BY_10_ENDPOINTS: + reading = reading / 10 + + return cls(endpoint, int(port), reading) + +def get_alternate_name(port): + try: + return PORT_NAMES[port] + except KeyError: + return port + +def snmp_walk(host): + proc = subprocess.Popen( + ["snmpwalk", "-Os", "-c", "tplink", "-v", "2c", "-m", "TPLINK-POWER-OVER-ETHERNET-MIB", host, "tplinkPowerOverEthernetMIB"], + stdout = subprocess.PIPE + ) + out = [] + while True: + line = proc.stdout.readline() + if not line: + break + try: + out.append(SNMPReading.from_string(line.rstrip().decode())) + except Exception: + pass + + return out + +def readings_to_points(readings, switch_host): + points = [] + df = pandas.DataFrame(readings) + df["port_name"] = df["port"].apply(get_alternate_name) + for p, group_df in df.groupby(["port", "port_name"]): + port, port_name = p + fields = dict(zip(group_df['endpoint'], group_df['reading'])) + + points.append({"measurement": "switch_status", "tags": {"port": port, "port_name": port_name, "switch_host": switch_host}, "fields": fields}) + + return points + +if __name__ == "__main__": + env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "config.env") + if os.path.exists(env_path): + import dotenv + dotenv.load_dotenv(dotenv_path = env_path) + INFLUXDB_HOST = "dns.athome" + else: + INFLUXDB_HOST = "influxdb" + + influxc = InfluxDBClient( + url = "http://%s:8086" % INFLUXDB_HOST, + token = os.environ["DOCKER_INFLUXDB_INIT_ADMIN_TOKEN"], + org = os.environ["DOCKER_INFLUXDB_INIT_ORG"] + ) + influxc.ping() + + for switch_host in os.environ["OMADA_SWITCHES"].split(","): + points = readings_to_points(snmp_walk(switch_host), switch_host) + write_api = influxc.write_api(write_options = SYNCHRONOUS) + write_api.write( + os.environ["DOCKER_INFLUXDB_INIT_BUCKET"], + os.environ["DOCKER_INFLUXDB_INIT_ORG"], + points, + write_precision = WritePrecision.S + )
\ No newline at end of file |