diff --git a/backend/app/influxdb_service.py b/backend/app/influxdb_service.py new file mode 100644 index 0000000000000000000000000000000000000000..fa31778f42e4fdc1735939c07aa53d448337514c --- /dev/null +++ b/backend/app/influxdb_service.py @@ -0,0 +1,14 @@ +import os +from dotenv import load_dotenv +from utils.influx import InfluxDBHelper + +load_dotenv() + +client = InfluxDBHelper( + url=os.getenv("INFLUXDB_URL"), + token=os.getenv("INFLUXDB_TOKEN"), + org=os.getenv("INFLUXDB_ORG"), + bucket=os.getenv("INFLUXDB_BUCKET"), +) + +client.get_all_data diff --git a/backend/pyproject.toml b/backend/pyproject.toml index ad8e396bc16e98eb919802d9989e75872d7d10e2..356ccde0f82bc4edc77fe7043a31aaeff46db714 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -2,12 +2,29 @@ name = "backend" version = "0.1.0" description = "backend of co2 web service" +authors = [ + {name = "Patrick Ade", email = "21adpa1bif@hft-stuttgart.de"}, + {name = "Morten Stetter", email = "22?stmo1bif@hft-stuttgart.de"}, + {name = "Aaron Mele", email = "21meaa1bif@hft-stuttgart.de"}, + {name = "Emre Gezer", email = "21geem1bif@hft-stuttgart.de"}, +] +maintainers = [ + {name = "Max Mustermann", email = "mustermann@example.com"} +] readme = "README.md" +keywords = ["co2", "sensor", "temperature", "humidity", "monitor", "monitoring", "air quality"] + requires-python = ">=3.12" -dependencies = ["django>=5.2", "django-cors-headers>=4.7.0"] +dependencies = [ + "django>=5.2", + "django-cors-headers>=4.7.0", + "influxdb-client>=1.40" +] [dependency-groups] -dev = ["ruff>=0.11.5"] +dev = [ + "ruff>=0.11.5" +] [tool.ruff] # Set the maximum line length to 80. diff --git a/backend/requirements.txt b/backend/requirements.txt index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5d7849df15ff0b27241d9258ea9afa7af2321690 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -0,0 +1,3 @@ +django>=5.2 +django-cors-headers>=4.7.0 +influxdb-client>=1.40 \ No newline at end of file diff --git a/backend/uv.lock b/backend/uv.lock index 436211b206277df637c528d23c5063e45417b2c1..bb5eac9050ed029ae4ff5fce08ba0f45b49c48a9 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -18,9 +18,10 @@ source = { virtual = "." } dependencies = [ { name = "django" }, { name = "django-cors-headers" }, + { name = "influxdb-client" }, ] -[package.dev-dependencies] +[package.optional-dependencies] dev = [ { name = "ruff" }, ] @@ -29,10 +30,19 @@ dev = [ requires-dist = [ { name = "django", specifier = ">=5.2" }, { name = "django-cors-headers", specifier = ">=4.7.0" }, + { name = "influxdb-client", specifier = ">=1.40" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.11.5" }, ] +provides-extras = ["dev"] -[package.metadata.requires-dev] -dev = [{ name = "ruff", specifier = ">=0.11.5" }] +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] [[package]] name = "django" @@ -61,6 +71,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/a2/7bcfff86314bd9dd698180e31ba00604001606efb518a06cca6833a54285/django_cors_headers-4.7.0-py3-none-any.whl", hash = "sha256:f1c125dcd58479fe7a67fe2499c16ee38b81b397463cf025f0e2c42937421070", size = 12794 }, ] +[[package]] +name = "influxdb-client" +version = "1.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "python-dateutil" }, + { name = "reactivex" }, + { name = "setuptools" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/47/b756380917cb4b968bd871fc006128e2cc9897fb1ab4bcf7d108f9601e78/influxdb_client-1.48.0.tar.gz", hash = "sha256:414d5b5eff7d2b6b453f33e2826ea9872ea04a11996ba9c8604b0c1df57c8559", size = 386415 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/b3/1edc89584b8d1bc5226cf508b67ab64da3ba83041cab348861e6f4392326/influxdb_client-1.48.0-py3-none-any.whl", hash = "sha256:410db15db761df7ea98adb333c7a03f05bcc2ceef4830cefb7071b888be2b827", size = 746177 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "reactivex" +version = "4.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/63/f776322df4d7b456446eff78c4e64f14c3c26d57d46b4e06c18807d5d99c/reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8", size = 119177 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/3f/2ed8c1b8fe3fc2ed816ba40554ef703aad8c51700e2606c139fcf9b7f791/reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a", size = 217791 }, +] + [[package]] name = "ruff" version = "0.11.5" @@ -86,6 +136,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/7c/c83fe5cbb70ff017612ff36654edfebec4b1ef79b558b8e5fd933bab836b/ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e", size = 10460287 }, ] +[[package]] +name = "setuptools" +version = "79.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/19/fecb7e2825616270f34512b3394cdcf6f45a79b5b6d94fdbd86a509e67b5/setuptools-79.0.0.tar.gz", hash = "sha256:9828422e7541213b0aacb6e10bbf9dd8febeaa45a48570e09b6d100e063fc9f9", size = 1367685 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/ea/d53f2f8897c46a36df085964d07761ea4c2d1f2cf92019693b6742b7aabb/setuptools-79.0.0-py3-none-any.whl", hash = "sha256:b9ab3a104bedb292323f53797b00864e10e434a3ab3906813a7169e4745b912a", size = 1256065 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + [[package]] name = "sqlparse" version = "0.5.3" @@ -95,6 +163,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 }, ] +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, +] + [[package]] name = "tzdata" version = "2025.2" @@ -103,3 +180,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be76 wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, ] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, +] diff --git a/mqtt/pyproject.toml b/mqtt/pyproject.toml index 70f1ca35436ac27b1d12bfbcc7f9bcbfb3721fb7..f3b0a3f5b8ecb896315a53255d0c6927836a8a66 100644 --- a/mqtt/pyproject.toml +++ b/mqtt/pyproject.toml @@ -3,6 +3,16 @@ name = "mqtt-influx-backend" version = "0.1.0" description = "Backend to write MQTT sensor data to InfluxDB" readme = "README.md" +authors = [ + {name = "Patrick Ade", email = "21adpa1bif@hft-stuttgart.de"}, + {name = "Morten Stetter", email = "22?stmo1bif@hft-stuttgart.de"}, + {name = "Aaron Mele", email = "21meaa1bif@hft-stuttgart.de"}, + {name = "Emre Gezer", email = "21geem1bif@hft-stuttgart.de"}, +] +maintainers = [ + {name = "Max Mustermann", email = "mustermann@example.com"} +] +keywords = ["co2", "sensor", "temperature", "humidity", "monitor", "monitoring", "air quality"] requires-python = ">=3.10" dependencies = [ "paho-mqtt", diff --git a/mqtt/src/mqtt_influx_backend/mQTTClientHandler.py b/mqtt/src/mqtt_influx_backend/mQTTClientHandler.py index ed4ffb3895ce64184ca89047666bad3da5cb7fd8..7695e7bbafd7bb645a8bb5e87c93fe1c3f2119d9 100644 --- a/mqtt/src/mqtt_influx_backend/mQTTClientHandler.py +++ b/mqtt/src/mqtt_influx_backend/mQTTClientHandler.py @@ -1,10 +1,10 @@ import json -from src.mqtt_influx_backend.loggingFactory import LoggerFactory +from mqtt_influx_backend.loggingFactory import LoggerFactory from datetime import datetime import paho.mqtt.client as mqtt -from src.mqtt_influx_backend import jsonhandler -from src.mqtt_influx_backend import influxDBWriter +from mqtt_influx_backend import jsonhandler +from utils.influx import InfluxDBHelper class MQTTClientHandler: @@ -17,7 +17,7 @@ class MQTTClientHandler: FIELD_HUMIDITY = "humidity" # Konstruktor - def __init__(self, broker_url: str, topic: str, influx_writer: influxDBWriter): + def __init__(self, broker_url: str, topic: str, influx_writer: InfluxDBHelper): self.logger = LoggerFactory.get_logger(__name__) # key: mac : value : room self.mac_to_room = jsonhandler.load_json(self.MAPPING_FILE_NAME) diff --git a/mqtt/src/mqtt_influx_backend/mac_to_room.json b/mqtt/src/mqtt_influx_backend/mac_to_room.json index 94dcdcf82e1112dbfc011e6aeb91e37db2b7b8a5..2e88a4cf09eb9f95cbaaeecf0b7cf6f79f2134cd 100644 --- a/mqtt/src/mqtt_influx_backend/mac_to_room.json +++ b/mqtt/src/mqtt_influx_backend/mac_to_room.json @@ -5,5 +5,8 @@ "DE:AD:BE:EF:12:34": "", "DK:AD:BE:EF:12:34": "", "EK:AD:BE:EF:12:34": "", - "lK:AD:BE:EF:12:34": "" + "lK:AD:BE:EF:12:34": "", + "MK:AD:BE:EF:12:34": "", + "AB:AD:BE:EF:12:34": "", + "EF:AD:BE:EF:12:34": "" } \ No newline at end of file diff --git a/mqtt/src/mqtt_influx_backend/main.py b/mqtt/src/mqtt_influx_backend/main.py index 168546e063365924d7877ef27373e195344e4e1b..bebebe8d1385bb7ffd66408fc27bb762cc4e61bc 100644 --- a/mqtt/src/mqtt_influx_backend/main.py +++ b/mqtt/src/mqtt_influx_backend/main.py @@ -1,14 +1,13 @@ from dotenv import load_dotenv import os -import logging -from src.mqtt_influx_backend.mQTTClientHandler import MQTTClientHandler -from src.mqtt_influx_backend.influxDBWriter import InfluxDBWriter +from mqtt_influx_backend.mQTTClientHandler import MQTTClientHandler +from utils.influx import InfluxDBHelper load_dotenv() def main(): - influx_writer = InfluxDBWriter( + influx_writer = InfluxDBHelper( url=os.getenv("INFLUXDB_URL"), token=os.getenv("INFLUXDB_TOKEN"), org=os.getenv("INFLUXDB_ORG"), diff --git a/utils/influx.py b/utils/influx.py new file mode 100644 index 0000000000000000000000000000000000000000..55aca620dfbf0c399e943b5a317aebae59220549 --- /dev/null +++ b/utils/influx.py @@ -0,0 +1,47 @@ +import os +from influxdb_client import InfluxDBClient, Point, WritePrecision +from influxdb_client.client.write_api import WriteOptions + +class InfluxDBHelper: + def __init__(self, url: str, token: str, org: str, bucket: str): + self.client = InfluxDBClient(url=url, token=token, org=org) + self.bucket = bucket + self.org = org + self.query_api = self.client.query_api() + # self.write_api = self.client.write_api(write_options=SYNCHRONOUS) good for debug + self.write_api = self.client.write_api(write_options=WriteOptions(batch_size=1000, flush_interval=10000)) + + def write_point(self, measurement: str, tags: dict, fields: dict, timestamp=None): + ''' + + ''' + point = Point(measurement) + for k, v in tags.items(): + point.tag(k, v) + for k, v in fields.items(): + point.field(k, v) + if timestamp: + point.time(timestamp, WritePrecision.NS) + self.write_api.write(bucket=self.bucket, org=self.org, record=point) + + def get_all_data(self): + ''' + ''' + query = f''' + from(bucket: "{self.bucket}") + |> range(start: -1h) + |> filter(fn: (r) => r["_measurement"] == "sensor_data") + ''' + return self.query_api.query(org=self.org, query=query) + + def get_latest_room_data(self, room_id: str): + ''' + ''' + query = f''' + from(bucket: "{self.bucket}") + |> range(start: -5m) + |> filter(fn: (r) => r["_measurement"] == "co2") + |> filter(fn: (r) => r["room"] == "{room_id}") + |> last() + ''' + return self.query_api.query(org=self.org, query=query) diff --git a/utils/init.py b/utils/init.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391