From 135802109ef68ee0d1b06f62856a8a2be0cccfd6 Mon Sep 17 00:00:00 2001 From: Laur Ivan Date: Mon, 19 Dec 2022 12:24:47 +0100 Subject: [PATCH] feat: Initial commit --- .env | 7 + .gitignore | 3 + Dockerfile | 26 ++++ LICENSE | 9 ++ README.md | 74 +++++++++++ db.py | 20 +++ export-addresses-guest-12-12.csv | 222 +++++++++++++++++++++++++++++++ knx_monitor.py | 82 ++++++++++++ requirements.txt | 20 +++ structure.py | 105 +++++++++++++++ 10 files changed, 568 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 db.py create mode 100644 export-addresses-guest-12-12.csv create mode 100644 knx_monitor.py create mode 100644 requirements.txt create mode 100644 structure.py diff --git a/.env b/.env new file mode 100644 index 0000000..dbeea4a --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +KNX_GATEWAY_IP=10.0.0.36 +KNX_TOPOLOGY=export-addresses.csv + +INFLUX_BUCKET=my_bucket +INFLUX_ORG=example.com +INFLUX_TOKEN=change_me +INFLUX_URL=http://localhost:8086 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0877b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +venv/ +requirements/ +__py*/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..42d7d08 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.10-alpine + +WORKDIR /code/knx + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir -r /code/requirements.txt + +ENV KNX_GATEWAY_IP=10.0.0.36 +ENV KNX_TOPOLOGY=export-addresses.csv + +ENV INFLUX_BUCKET=my_bucket +ENV INFLUX_ORG=example.com +ENV INFLUX_TOKEN=change_me +ENV INFLUX_URL=http://localhost:8086 + +COPY ./*.py /code/knx +COPY ./.env /code/knx + + +ENV CONFIG_STRUCTURE=/code/data/export-addresses.csv + +VOLUME ["/code/data/export-addresses.csv", "/code/knx/.env"] + + +CMD ["python", "knx_monitor.py"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7656a24 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2022 Laur IVAN + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fc6a9b --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# KNX monitor + +## About + +This program collects [KNX](https://www.knx.org/knx-en/for-your-home/) data into [InfluxDB](https://www.influxdata.com/products/influxdb-overview/) + +## Requirements + +You need an IP router/gateway accessible from your network. You also need InfluxDB (and [grafana](https://grafana.com/)) installed so data can be uploaded. + +Tou also need to install the requirements: + +```bash +pip install --no-cache-dir -r .requirements.txt +``` + +## Usage + +You need to configure the following environment variables: + +- `KNX_GATEWAY_IP` is the address of the gateway/router. Default value is *10.0.0.36* +- `KNX_TOPOLOGY` is the CSV export of the group adresses. It's used to map the monitored data with types and store the proper values. It defaults to *export-addresses.csv*. PLease see below how to export the right format. + +- `INFLUX_BUCKET` - the bucket to store the data. It defaults to *my_bucket* +- `INFLUX_ORG` - the organisation (defined when you initialise the bucket). Defaults to *example.com* +- `INFLUX_TOKEN` - the token to access the InfluxDB instance. Defaults to *change_me* +- `INFLUX_URL` the URL where InfluxDB is located. It defaults to `http://localhost:8086` + +### Export the topology + +Here are the steps to export the topology in the proper format: + +1. Open ETS +1. Open your project +1. Open a **Group Addresses** panel +1. Right clock on the top **Group Addresses** (just above *Dynamic Folders*) +1. Click on **Export Group Addresses** +1. Choose **CSV Format**, **1/1 Name/Address** with a **Semicolon** CSV Separator +1. Select a file name to store the result +1. click "OK" + +You should have a CSV file like: + +```csv +"Group name";"Address";"Central";"Unfiltered";"Description";"DatapointType";"Security" +"Cellar - HVAC";"0/-/-";"";"";"";"";"Auto" +"Media - HVAC";"0/3/-";"";"";"";"";"Auto" +"Valve1-1 Media [CTEMP 1.3.10]";"0/3/6";"";"";"Wellness Lounge H";"DPST-9-1";"Auto" +"Valve1-1 Media [SETPOINT TEMP]";"0/3/7";"";"";"Wellness Lounge H";"DPST-9-1";"Auto" +"Valve1-1 Media [CMD]";"0/3/8";"";"";"Wellness Lounge H";"DPST-5-1";"Auto" +"Valve1-1 Media [OP]";"0/3/9";"";"";"Wellness Lounge H";"DPST-20-102";"Auto" +``` + +### Launch + +You can either use the command line: + +```shell +python3 knx_monitor +``` + +or use the provided docker image. In this case, please make sure you set the read-only volume to the export file + +```shell +-v /path/to/your/export.csv:/code/data/export-addresses.csv +``` + +You can either define the above environment variables, or create a .env file and map it via + +```shell +-v /path/to/your/envfile:/code/knx/.env +``` + +HTH diff --git a/db.py b/db.py new file mode 100644 index 0000000..e5a15ab --- /dev/null +++ b/db.py @@ -0,0 +1,20 @@ +import influxdb_client +from influxdb_client.client.write_api import SYNCHRONOUS +from dotenv import load_dotenv +import os + +dotenv_path = join(dirname(__file__), '.env') +load_dotenv(dotenv_path) + +bucket=os.environ.get('INFLUX_BUCKET') +org=os.environ.get('INFLUX_ORG') +token=os.environ.get('INFLUX_TOKEN') +url=os.environ.get('INFLUX_URL') + +client = influxdb_client.InfluxDBClient(url=url, token=token, org=org) +write_api = client.write_api(write_options=SYNCHRONOUS) + + +def publish_measurement(value): + p = influxdb_client.Point(f'KNX-{value["type"]}').tag("name", f'{value["name"]}').tag("destination", f'{value["destination"]}').tag("description", f'{value["description"]}').field("value", value["value"]) + write_api.write(bucket=bucket, org=org, record=p) \ No newline at end of file diff --git a/export-addresses-guest-12-12.csv b/export-addresses-guest-12-12.csv new file mode 100644 index 0000000..43bbbbd --- /dev/null +++ b/export-addresses-guest-12-12.csv @@ -0,0 +1,222 @@ +"Group name";"Address";"Central";"Unfiltered";"Description";"DatapointType";"Security" +"Cellar - HVAC";"0/-/-";"";"";"";"";"Auto" +"Media - HVAC";"0/3/-";"";"";"";"";"Auto" +"Valve1-1 Media [CTEMP 1.3.10]";"0/3/6";"";"";"Wellness Lounge H";"DPST-9-1";"Auto" +"Valve1-1 Media [SETPOINT TEMP]";"0/3/7";"";"";"Wellness Lounge H";"DPST-9-1";"Auto" +"Valve1-1 Media [CMD]";"0/3/8";"";"";"Wellness Lounge H";"DPST-5-1";"Auto" +"Valve1-1 Media [OP]";"0/3/9";"";"";"Wellness Lounge H";"DPST-20-102";"Auto" +"Wellness - HVAC";"0/5/-";"";"";"";"";"Auto" +"Valve1-2 Lounge [CTEMP 2.3.1]";"0/5/2";"";"";"Wellness HVAC - Lounge";"DPST-9-1";"Auto" +"Valve1-2 Lounge [SETPOINT TEMP]";"0/5/3";"";"";"Wellness HVAC - Lounge";"DPST-9-1";"Auto" +"Valve1-2 Lounge [CMD]";"0/5/4";"";"";"Wellness HVAC - Lounge";"DPST-5-1";"Auto" +"Valve1-2 Lounge [OP]";"0/5/5";"";"";"Wellness HVAC - Lounge";"DPST-20-102";"Auto" +"Ground floor";"2/-/-";"";"";"";"";"Auto" +"Dining";"2/3/-";"";"";"";"";"Auto" +"Lights1-1[78] Dining all lights [SW]";"2/3/4";"";"";"Dining room All";"DPST-1-1";"Auto" +"Lights1-1[78] Dining all lights [FB]";"2/3/5";"";"";"Dining room All";"DPST-1-1";"Auto" +"Living";"2/6/-";"";"";"";"";"Auto" +"Lights1-[16,18,20,21] Living all [SW]";"2/6/8";"";"";"Living room All";"DPST-1-1";"Auto" +"Lights1-[16,18,20,21] Living all [FB]";"2/6/9";"";"";"Living room All";"DPST-1-1";"Auto" +"First floor - HVAC";"3/-/-";"";"";"";"";"Auto" +"Master bed - HVAC";"3/1/-";"";"";"";"";"Auto" +"Valve2-2 Master bed [CTEMP 2.2.1]";"3/1/14";"";"";"Master HVAC";"DPST-9-1";"Auto" +"Valve2-2 Master bed [SETPOINT TEMP]";"3/1/15";"";"";"Master HVAC";"DPST-9-1";"Auto" +"Valve2-2 Master bed [CMD]";"3/1/16";"";"";"Master HVAC";"DPST-5-1";"Auto" +"Valve2-2 Master bed [OP]";"3/1/17";"";"";"Master HVAC";"DPST-20-102";"Auto" +"Bathroom - HVAC";"3/2/-";"";"";"";"";"Auto" +"Valve2-1 Bathroom [CTEMP 2.2.3]";"3/2/6";"";"";"Bathroom HVAC Bath";"DPST-9-1";"Auto" +"Valve2-1 Bathroom [SETPOINT TEMP]";"3/2/7";"";"";"Bathroom HVAC Bath";"DPST-9-1";"Auto" +"Valve2-1 Bathroom [CMD]";"3/2/8";"";"";"Bathroom HVAC Bath";"DPST-5-1";"Auto" +"Valve2-1 Bathroom [OP]";"3/2/9";"";"";"Bathroom HVAC Bath";"DPST-20-102";"Auto" +"Child - HVAC";"3/3/-";"";"";"";"";"Auto" +"Valve2-4 Child [CTEMP 2.2.2]";"3/3/6";"";"";"Child HVAC Child";"DPST-9-1";"Auto" +"Valve2-4 Child [SETPOINT TEMP]";"3/3/7";"";"";"Child HVAC Child";"DPST-9-1";"Auto" +"Valve2-4 Child [CMD]";"3/3/8";"";"";"Child HVAC Child";"DPST-5-1";"Auto" +"Valve2-4 Child [OP]";"3/3/9";"";"";"Child HVAC Child";"DPST-20-102";"Auto" +"Study - HVAC";"3/5/-";"";"";"";"";"Auto" +"Valve2-3 Study [CTEMP 2.2.4]";"3/5/6";"";"";"Study HVAC";"DPST-9-1";"Auto" +"Valve2-3 Study [SETPOINT TEMP]";"3/5/7";"";"";"Study HVAC";"DPST-9-1";"Auto" +"Valve2-3 Study [CMD]";"3/5/8";"";"";"Study HVAC";"DPST-5-1";"Auto" +"Valve2-3 Study [OP]";"3/5/9";"";"";"Study HVAC";"DPST-20-102";"Auto" +"HVAC";"4/-/-";"";"";"";"";"Auto" +"Media Room";"4/0/-";"";"";"";"";"Auto" +"In house";"4/0/0";"";"";"";"DPST-20-102";"Auto" +"21 degrees rooms";"4/0/1";"";"";"";"DPST-9-1";"Auto" +"23 degrees bathrooms";"4/0/2";"";"";"";"DPST-9-1";"Auto" +"Presence led";"4/0/3";"";"";"";"DPST-1-1";"Auto" +"UG Media";"4/0/4";"";"";"";"DPST-5-1";"Auto" +"UG Lounge";"4/0/5";"";"";"";"DPST-5-1";"Auto" +"GF - Shower";"4/0/6";"";"";"";"DPST-5-1";"Auto" +"GF - Dining";"4/0/7";"";"";"";"DPST-5-1";"Auto" +"GF - Living";"4/0/8";"";"";"";"DPST-5-1";"Auto" +"GF - Guest";"4/0/9";"";"";"";"DPST-5-1";"Auto" +"All valves";"4/0/101";"";"";"";"DPST-5-1";"Auto" +"Setpoint temperature";"4/2/-";"";"";"";"";"Auto" +"UG Media";"4/2/0";"";"";"";"DPST-9-1";"Auto" +"UG Lounge";"4/2/1";"";"";"";"DPST-9-1";"Auto" +"GF - Shower";"4/2/2";"";"";"";"DPST-9-1";"Auto" +"GF - Dining";"4/2/3";"";"";"";"DPST-9-1";"Auto" +"GF - Living";"4/2/4";"";"";"";"DPST-9-1";"Auto" +"GF - Guest";"4/2/5";"";"";"";"DPST-9-1";"Auto" +"FF - Master bed";"4/2/6";"";"";"";"DPST-9-1";"Auto" +"FF - Child";"4/2/7";"";"";"";"DPST-9-1";"Auto" +"Valve position";"4/3/-";"";"";"";"";"Auto" +"UG Media";"4/3/0";"";"";"";"DPST-5-1";"Auto" +"UG Lounge";"4/3/1";"";"";"";"DPST-5-1";"Auto" +"GF - Shower";"4/3/2";"";"";"";"DPST-5-1";"Auto" +"GF - Dining";"4/3/3";"";"";"";"DPST-5-1";"Auto" +"GF - Living";"4/3/4";"";"";"";"DPST-5-1";"Auto" +"GF - Guest";"4/3/5";"";"";"";"DPST-5-1";"Auto" +"FF - Master bed";"4/3/6";"";"";"";"DPST-5-1";"Auto" +"FF - Child";"4/3/7";"";"";"";"DPST-5-1";"Auto" +"UG - Building based";"5/-/-";"";"";"";"";"Auto" +"Scene";"5/0/-";"";"";"";"";"Auto" +"Lights1-[567] - Media all lights SW";"5/0/0";"";"";"Media all lamps switching feedback";"DPST-1-1";"Auto" +"Lights1-[567] - Media all lights FB";"5/0/1";"";"";"Media all lamps switching feedback +Sender done via NodeRed.";"DPST-1-1";"Auto" +"Lighting";"5/1/-";"";"";"";"";"Auto" +"Lights1-1 Tech room SW";"5/1/0";"";"";"";"DPST-1-1";"Auto" +"Lights1-1 Tech room FB";"5/1/1";"";"";"";"DPST-1-1";"Auto" +"Lights1-2 Storage SW";"5/1/2";"";"";"";"DPST-1-1";"Auto" +"Lights1-2 Storage FB";"5/1/3";"";"";"";"DPST-1-1";"Auto" +"Lights1-3 Server SW";"5/1/4";"";"";"";"DPST-1-1";"Auto" +"Lights1-3 Server FB";"5/1/5";"";"";"";"DPST-1-1";"Auto" +"Lights1-4 Hall SW";"5/1/6";"";"";"";"DPST-1-1";"Auto" +"Lights1-4 Hall FB";"5/1/7";"";"";"";"DPST-1-1";"Auto" +"Lights1-5 Media back SW";"5/1/8";"";"";"";"DPST-1-1";"Auto" +"Lights1-5 Media back FB";"5/1/9";"";"";"";"DPST-1-1";"Auto" +"Lights1-6 Media mid SW";"5/1/10";"";"";"";"DPST-1-1";"Auto" +"Lights1-6 Media mid FB";"5/1/11";"";"";"";"DPST-1-1";"Auto" +"Lights1-7 Media front SW";"5/1/12";"";"";"";"DPST-1-1";"Auto" +"Lights1-7 Media front FB";"5/1/13";"";"";"";"DPST-1-1";"Auto" +"Shutter";"5/2/-";"";"";"";"";"Auto" +"Heating";"5/3/-";"";"";"";"";"Auto" +"Timers";"5/4/-";"";"";"";"";"Auto" +"GF - Building Based";"6/-/-";"";"";"";"";"Auto" +"Scene";"6/0/-";"";"";"";"";"Auto" +"Lights1-2[34] Shower, all lights [SW]";"6/0/0";"";"";"Shower both lamps switching";"DPST-1-1";"Auto" +"Lights1-2[34] Shower, all lights [FB]";"6/0/1";"";"";"Shower both lamps switching feedback +Feedback received via NodeRed";"DPST-1-1";"Auto" +"Lights1-1[45] Kitchen, all lights [SW]";"6/0/2";"";"";"";"DPST-1-1";"Auto" +"Lights1-1[45] Kitchen, all lights [FB]";"6/0/3";"";"";"";"DPST-1-1";"Auto" +"Lights[1-13,2-13] Hallway [SW]";"6/0/4";"";"";"";"DPST-1-1";"Auto" +"Lights[1-13,2-13] Hallway [FB]";"6/0/5";"";"";"";"DPST-1-1";"Auto" +"Lighting";"6/1/-";"";"";"";"";"Auto" +"Lights1-8 Guest, Ceiling lamp [SW]";"6/1/0";"";"";"Guest ceiling lamp switching";"DPST-1-1";"Auto" +"Lights1-8 Guest, Ceiling lamp [FB]";"6/1/1";"";"";"Guest ceiling lamp switching feedback";"DPST-1-1";"Auto" +"Lights1-23 Shower, Ceiling lamp [SW]";"6/1/2";"";"";"Shower ceiling lamp switching";"DPST-1-1";"Auto" +"Lights1-23 Shower, Ceiling lamp [FB]";"6/1/3";"";"";"Shower ceiling lamp switching feedback";"DPST-1-1";"Auto" +"Lights1-24 Shower, Mirror lamp [SW]";"6/1/4";"";"";"Shower mirror lamp switching";"DPST-1-1";"Auto" +"Lights1-24 Shower, Mirror lamp [FB]";"6/1/5";"";"";"Shower mirror lamp switching";"DPST-1-1";"Auto" +"Lights1-9 Storage, Ceiling lamp [SW]";"6/1/6";"";"";"Storage ceiling lamps switching";"DPST-1-1";"Auto" +"Lights1-9 Storage, Ceiling lamp [FB]";"6/1/7";"";"";"Storage ceiling lamps feedback";"DPST-1-1";"Auto" +"Lights1-14 Kitchen ceiling [SW]";"6/1/8";"";"";"";"DPST-1-1";"Auto" +"Lights1-14 Kitchen ceiling [FB]";"6/1/9";"";"";"";"DPST-1-1";"Auto" +"Lights1-22 Staircase [SW]";"6/1/10";"";"";"";"DPST-1-1";"Auto" +"Lights1-22 Staircase [FB]";"6/1/11";"";"";"";"DPST-1-1";"Auto" +"Light1-14 Kitchen rail [SW]";"6/1/12";"";"";"";"DPST-1-1";"Auto" +"Light1-14 Kitchen rail [FB]";"6/1/13";"";"";"";"DPST-1-1";"Auto" +"Lights1-17 Dining rail [SW]";"6/1/14";"";"";"Dining room Beam";"DPST-1-1";"Auto" +"Lights1-17 Dining rail [FB]";"6/1/15";"";"";"Dining room Beam";"DPST-1-1";"Auto" +"Lights1-18 Dining top [SW]";"6/1/16";"";"";"Dining room Top";"DPST-1-1";"Auto" +"Lights1-18 Dining top [FB]";"6/1/17";"";"";"Dining room Top";"DPST-1-1";"Auto" +"Lights1-16 Living hall [SW]";"6/1/18";"";"";"Living room Living hall";"DPST-1-1";"Auto" +"Lights1-16 Living hall [FB]";"6/1/19";"";"";"Living room Living hall";"DPST-1-1";"Auto" +"Lights1-19 Living main [SW]";"6/1/20";"";"";"Living room Living main";"DPST-1-1";"Auto" +"Lights1-19 Living main [FB]";"6/1/21";"";"";"Living room Living main";"DPST-1-1";"Auto" +"Lights1-20 Living beam [SW]";"6/1/22";"";"";"Living room Living rail";"DPST-1-1";"Auto" +"Lights1-20 Living beam [FB]";"6/1/23";"";"";"Living room Living rail";"DPST-1-1";"Auto" +"Lights1-21 Living back [SW]";"6/1/24";"";"";"Living room Living accent";"DPST-1-1";"Auto" +"Lights1-21 Living back [FB]";"6/1/25";"";"";"Living room Living accent";"DPST-1-1";"Auto" +"Shutter";"6/2/-";"";"";"";"";"Auto" +"HVAC";"6/3/-";"";"";"";"";"Auto" +"Valve1 - 6 Living heating [CTEMP 2.1.11]";"6/3/0";"";"";"Living accents";"DPST-9-1";"Auto" +"Valve1 - 6 Living heating [CTEMP 2.1.13]";"6/3/1";"";"";"Living stairs";"DPST-9-1";"Auto" +"Valve1 - 6 Living heating [CTEMP 2.1.10]";"6/3/2";"";"";"Living main";"DPST-9-1";"Auto" +"Valve1 - 6 Living heating [CMD]";"6/3/3";"";"";"Living room HVAC";"DPST-5-1";"Auto" +"Valve1 - 6 Living heating [SETPOINT TEMP]";"6/3/4";"";"";"Living room HVAC";"DPST-9-1";"Auto" +"Valve1 - 6 Living heating [OP]";"6/3/5";"";"";"Living room HVAC";"DPST-20-102";"Auto" +"Valve1 - 5 Dining heating [CTEMP 2.1.8]";"6/3/6";"";"";"Dining thermo";"DPST-9-1";"Auto" +"Valve1 - 5 Dining heating [CTEMP 2.1.9]";"6/3/7";"";"";"Dining door";"DPST-9-1";"Auto" +"Valve1 - 5 Dining heating [CMD]";"6/3/8";"";"";"Dining room HVAC";"DPST-5-1";"Auto" +"Valve1 - 5 Dining heating [SETPOINT TEMP]";"6/3/9";"";"";"Dining room HVAC";"DPST-9-1";"Auto" +"Valve1 - 5 Dining heating [OP]";"6/3/10";"";"";"Dining room HVAC";"DPST-20-102";"Auto" +"Valve1-4 Shower [CTEMP 2.1.7]";"6/3/11";"";"";"Shower";"DPST-9-1";"Auto" +"Valve1-4 Shower [CMD]";"6/3/12";"";"";"Shower HVAC";"DPST-5-1";"Auto" +"Valve1-4 Shower [STEPOINT TEMP]";"6/3/13";"";"";"Shower HVAC";"DPST-9-1";"Auto" +"Valve1-4 Shower [OP]";"6/3/14";"";"";"Shower HVAC";"DPST-20-102";"Auto" +"Valve1-3 Guest [CTEMP 2.1.1]";"6/3/15";"";"";"Guest room";"DPST-9-1";"Auto" +"Valve1-3 Guest [STEPOINT TEMP]";"6/3/16";"";"";"Guest room HVAC - Guest";"DPST-9-1";"Auto" +"Valve1-3 Guest [CMD]";"6/3/17";"";"";"Guest room HVAC - Guest";"DPST-5-1";"Auto" +"Valve1-3 Guest [OP]";"6/3/18";"";"";"Guest room HVAC - Guest";"DPST-20-102";"Auto" +"Valve1-X Entrance [CTEMP 2.1.2]";"6/3/19";"";"";"Entrance";"DPST-9-1";"Auto" +"Valve1-X Entrance [CTEMP 2.1.3]";"6/3/20";"";"";"Hallway";"DPST-9-1";"Auto" +"Valve1-X Storage [CTEMP 2.1.4]";"6/3/21";"";"";"Storage";"DPST-9-1";"Auto" +"Valve1-X Kitchen [CTEMP 2.1.5]";"6/3/22";"";"";"Kitchen entrance";"DPST-9-1";"Auto" +"Valve1-X Stairway [CTEMP 2.1.6]";"6/3/23";"";"";"Stairway GF";"DPST-9-1";"Auto" +"Valve1-X Slider [CTEMP 2.1.12]";"6/3/24";"";"";"Slider";"DPST-9-1";"Auto" +"Valve1-X Kitchen front [CTEMP 2.1.14]";"6/3/25";"";"";"Kitchen front";"DPST-9-1";"Auto" +"Timers";"6/4/-";"";"";"";"";"Auto" +"Current Time";"6/4/0";"";"";"";"DPST-10-1";"Auto" +"Led night reduction - Guest";"6/4/1";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Hallway";"6/4/2";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Shower";"6/4/3";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Kitchen";"6/4/4";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Dining";"6/4/5";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Living";"6/4/6";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Stairs";"6/4/7";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Storage";"6/4/8";"";"";"";"DPST-1-1";"Auto" +"FF - Building Based";"7/-/-";"";"";"";"";"Auto" +"Scene";"7/0/-";"";"";"";"";"Auto" +"Lights2-[567] Master bed, all lights SW";"7/0/0";"";"";"Master bedroom all lamps switching feedback";"DPST-1-1";"Auto" +"Lights2-[567] Master bed, all lights FB";"7/0/1";"";"";"Master bedroom all lamps switching feedback +Feedback received via NodeRed";"DPST-1-1";"Auto" +"Lights2-[4,11] Study all lights SW";"7/0/2";"";"";"";"DPST-1-1";"Auto" +"Lights2-[4,11] Study all lights FB";"7/0/3";"";"";"";"DPST-1-1";"Auto" +"Lighting";"7/1/-";"";"";"";"";"Auto" +"Lights2-7 Master bed Ceiling 1 SW";"7/1/0";"";"";"";"DPST-1-1";"Auto" +"Lights2-7 Master bed Ceiling 1 FB";"7/1/1";"";"";"";"DPST-1-1";"Auto" +"Lights2-6 Master bed Ceiling 2 SW";"7/1/2";"";"";"";"DPST-1-1";"Auto" +"Lights2-6 Master bed Ceiling 2 FB";"7/1/3";"";"";"";"DPST-1-1";"Auto" +"Lights2-5 Master bed Beam SW";"7/1/4";"";"";"";"DPST-1-1";"Auto" +"Lights2-5 Master bed Beam FB";"7/1/5";"";"";"";"DPST-1-1";"Auto" +"Lights2-8 Storage SW";"7/1/6";"";"";"";"DPST-1-1";"Auto" +"Lights2-8 Storage FB";"7/1/7";"";"";"";"DPST-1-1";"Auto" +"Lights2-15 Bath Rail SW";"7/1/8";"";"";"";"DPST-1-1";"Auto" +"Lights2-15 Bath Rail FB";"7/1/9";"";"";"";"DPST-1-1";"Auto" +"Lights2-15 Bath mirror backlit SW";"7/1/10";"";"";"";"DPST-1-1";"Auto" +"Lights2-15 Bath mirror backlit FB";"7/1/11";"";"";"";"DPST-1-1";"Auto" +"Lights2-15 Bath focus SW";"7/1/12";"";"";"";"DPST-1-1";"Auto" +"Lights2-15 Bath focus FB";"7/1/13";"";"";"";"DPST-1-1";"Auto" +"Lights2-1 Kid rail SW";"7/1/14";"";"";"";"DPST-1-1";"Auto" +"Lights2-1 Kid rail FB";"7/1/15";"";"";"";"DPST-1-1";"Auto" +"Lights2-1 Kid ceiling SW";"7/1/16";"";"";"";"DPST-1-1";"Auto" +"Lights2-1 Kid ceiling FB";"7/1/17";"";"";"";"DPST-1-1";"Auto" +"Lights2-1 Kid wall SW";"7/1/18";"";"";"";"DPST-1-1";"Auto" +"Lights2-1 Kid wall FB";"7/1/19";"";"";"";"DPST-1-1";"Auto" +"Lights2-12 Hallway SW";"7/1/20";"";"";"";"DPST-1-1";"Auto" +"Lights2-12 Hallway FB";"7/1/21";"";"";"";"DPST-1-1";"Auto" +"Light2-4 Study rail SW";"7/1/22";"";"";"";"DPST-1-1";"Auto" +"Light2-4 Study rail FB";"7/1/23";"";"";"";"DPST-1-1";"Auto" +"Light11-4 Study stairs SW";"7/1/24";"";"";"";"DPST-1-1";"Auto" +"Light11-4 Study stairs FB";"7/1/25";"";"";"";"DPST-1-1";"Auto" +"Shutter";"7/2/-";"";"";"";"";"Auto" +"Heating";"7/3/-";"";"";"";"";"Auto" +"Timers";"7/4/-";"";"";"";"";"Auto" +"Led night reduction - Master bed";"7/4/0";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Child bed";"7/4/1";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Open space";"7/4/2";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Bathroom";"7/4/3";"";"";"";"DPST-1-1";"Auto" +"Led night reduction - Storage";"7/4/4";"";"";"";"DPST-1-1";"Auto" +"Outside - Building based";"8/-/-";"";"";"";"";"Auto" +"Scene";"8/0/-";"";"";"";"";"Auto" +"Lighting";"8/1/-";"";"";"";"";"Auto" +"Lights1-10 Out left SW";"8/1/0";"";"";"";"DPST-1-1";"Auto" +"Lights1-10 Out left FB";"8/1/1";"";"";"";"DPST-1-1";"Auto" +"Lights2-14 Out entrance SW";"8/1/2";"";"";"";"DPST-1-1";"Auto" +"Lights2-14 Out entrance FB";"8/1/3";"";"";"";"DPST-1-1";"Auto" +"Lights1-10,2-14 Out SW";"8/1/4";"";"";"";"DPST-1-1";"Auto" +"Lights1-10,2-14 Out FB";"8/1/5";"";"";"";"DPST-1-1";"Auto" +"Shutter";"8/2/-";"";"";"";"";"Auto" +"Heating";"8/3/-";"";"";"";"";"Auto" +"Timers";"8/4/-";"";"";"";"";"Auto" diff --git a/knx_monitor.py b/knx_monitor.py new file mode 100644 index 0000000..61fc896 --- /dev/null +++ b/knx_monitor.py @@ -0,0 +1,82 @@ +"""Example for the telegram monitor callback.""" +import asyncio +import getopt +import sys + +from xknx import XKNX +from xknx.io import ConnectionConfig, ConnectionType +from xknx.telegram import AddressFilter +from structure import parse_message, load_structure, init_value_mapping +import json + +from dotenv import load_dotenv +dotenv_path = join(dirname(__file__), '.env') +load_dotenv(dotenv_path) + +from db import publish_measurement + +config = {} +mapping = {} + + + + +async def telegram_received_cb(telegram): + """Do something with the received telegram.""" + val = parse_message(config, mapping, telegram) + publish_measurement(val) + + +def show_help(): + """Print Help.""" + print("Telegram filter.") + print("") + print("Usage:") + print("") + print(__file__, " Listen to all telegrams") + print( + __file__, "-f --filter 1/2/*,1/4/[5-6] Filter for specific group addresses" + ) + print(__file__, "-h --help Print help") + print("") + + +async def monitor(address_filters): + """Set telegram_received_cb within XKNX and connect to KNX/IP device in daemon mode.""" + connection_config = ConnectionConfig( + connection_type=ConnectionType.TUNNELING, + gateway_ip=os.environ.get('KNX_GATEWAY_IP') + ) + xknx = XKNX(connection_config=connection_config, daemon_mode=True) + xknx.telegram_queue.register_telegram_received_cb( + telegram_received_cb, address_filters + ) + await xknx.start() + await xknx.stop() + + +async def main(argv): + """Parse command line arguments and start monitor.""" + try: + opts, _ = getopt.getopt(argv, "hf:", ["help", "filter="]) + except getopt.GetoptError: + show_help() + sys.exit(2) + + global config + config = load_structure(os.environ.get('KNX_TOPOLOGY')) + global mapping + mapping = init_value_mapping() + + address_filters = None + for opt, arg in opts: + if opt in ["-h", "--help"]: + show_help() + sys.exit() + if opt in ["-f", "--filter"]: + address_filters = list(map(AddressFilter, arg.split(","))) + await monitor(address_filters) + + +if __name__ == "__main__": + asyncio.run(main(sys.argv[1:])) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ce62670 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,20 @@ +certifi==2022.12.7 +cffi==1.15.1 +charset-normalizer==2.1.1 +cryptography==38.0.4 +idna==3.4 +ifaddr==0.2.0 +influxdb==5.3.1 +influxdb-client==1.35.0 +json-fix==0.5.1 +msgpack==1.0.4 +pycparser==2.21 +python-dateutil==2.8.2 +python-dotenv==0.21.0 +pytz==2022.6 +reactivex==4.0.4 +requests==2.28.1 +six==1.16.0 +typing-extensions==4.4.0 +urllib3==1.26.13 +xknx==2.1.0 diff --git a/structure.py b/structure.py new file mode 100644 index 0000000..c78a649 --- /dev/null +++ b/structure.py @@ -0,0 +1,105 @@ +import csv +import json +import json_fix + +import xknx.remote_value +import xknx +from xknx.dpt import DPT2ByteFloat, DPTBinary, DPTTime, DPTValue1ByteUnsigned + +from datetime import datetime + +class DPST: + def __init__(self, str): + vals = str.split('-') + self.major = vals[1] + self.minor = vals[2] + + def __str__(self): + return f"DPST {self.major}.{self.minor}" + + def __json__(self, **options): + return self.__dict__ + +class Endpoint: + def __init__(self, name, address, central, unfiltered, description, dpt, security, *args, **kwargs): + self.name = name + self.address = address + self.central = central + self.unfiltered = unfiltered + self.description = description + + if self.is_endpoint(): + self.dpt = DPST(dpt) + else: + self.dpt = None + self.security = security + + def is_endpoint(self): + return self.address[-1] != '-' + + def __str__(self): + return f"name: {self.name}, address: {self.address}, dpt: {self.dpt}" + + def __json__(self, **options): + return self.__dict__ + + +def init_value_mapping(): + config = {} + config['1'] = { "value": DPTBinary, "type": "number" } + config['9'] = { "value": DPT2ByteFloat, "type": "number" } + config['10'] = { "value": DPTTime, "type": "time" } + config['5'] = { "value": DPTValue1ByteUnsigned, "type": "number" } + + + return config + + +def load_structure(filename): + config = {} + with open(filename, newline='') as csvFile: + line = 0 + reader = csv.reader(csvFile, delimiter=';', quotechar='"') + for row in reader: + line += 1 + if line == 1: + continue + c = Endpoint(*row) + if c.is_endpoint(): + if c.dpt == '': + print(f"Unknown DPT format for {c.address} ({c.name})") + config[c.address] = c + return config + +def parse_message(structure, mapping, telegram): + if f"{telegram.destination_address}" not in structure: + print(f"Unrecognised address {telegram.destination_address}. ignoring") + return + hit = structure[f"{telegram.destination_address}"] + try: + value = mapping[hit.dpt.major]["value"].from_knx(telegram.payload.value.value) + + # Convert the value to something else if needed + # + if mapping[hit.dpt.major]["type"] == "time": + value = value.tm_hour*3600+value.tm_min*60 +value.tm_sec + + return { + "timestamp": datetime.now().timestamp(), + "value": value, + "destination": f"{telegram.destination_address}", + "name": hit.name, + "description": hit.description, + "type": f"{hit.dpt}" + } + except Exception as e: + print(f"Unmapped {hit.dpt}") + print (e) + + +if __name__ == '__main__': + from dotenv import load_dotenv + dotenv_path = join(dirname(__file__), '.env') + load_dotenv(dotenv_path) + config = load_structure(os.environ.get('KNX_TOPOLOGY')) + print(json.dumps(config["6/4/0"], indent=2))