async-httpd-data-collector 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ahttpdc/__init__.py +1 -0
- ahttpdc/reads/__init__.py +0 -0
- ahttpdc/reads/fetch/__init__.py +0 -0
- ahttpdc/reads/fetch/async_fetch.py +194 -0
- ahttpdc/reads/interface.py +168 -0
- ahttpdc/reads/query/__init__.py +0 -0
- ahttpdc/reads/query/async_query.py +222 -0
- async_httpd_data_collector-0.1.dist-info/METADATA +743 -0
- async_httpd_data_collector-0.1.dist-info/RECORD +11 -0
- async_httpd_data_collector-0.1.dist-info/WHEEL +4 -0
- async_httpd_data_collector-0.1.dist-info/licenses/LICENSE +674 -0
ahttpdc/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module asyncronously fetches the data from given device and writes the readings
|
|
3
|
+
into InfluxDB as records.
|
|
4
|
+
|
|
5
|
+
Author: Piotr Krzysztof Lis - github.com/straightchlorine
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import datetime
|
|
10
|
+
|
|
11
|
+
import aiohttp
|
|
12
|
+
from influxdb_client.client.influxdb_client_async import InfluxDBClientAsync
|
|
13
|
+
from influxdb_client.client.write.point import Point
|
|
14
|
+
|
|
15
|
+
__all__ = ["AsyncReadFetcher"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AsyncReadFetcher:
|
|
19
|
+
"""
|
|
20
|
+
Manage fetching the readings from the sensor asyncronously via aiohttp and
|
|
21
|
+
handling received data so that it can be stored within InfluxDB.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
_influxdb_host (str): The host of the InfluxDB instance.
|
|
25
|
+
_influxdb_port (int): The port of the InfluxDB instance.
|
|
26
|
+
_influxdb_token (str): The token to authenticate with InfluxDB.
|
|
27
|
+
_influxdb_organization (str): The organization to use within InfluxDB.
|
|
28
|
+
_influxdb_bucket (str): Bucket within InfluxDB where the data will be stored.
|
|
29
|
+
_device_ip (str): The IP address of device providing sensor readings.
|
|
30
|
+
_device_port (str): The port of the device providing the readings.
|
|
31
|
+
_http_handle (str): The http handle to access the data.
|
|
32
|
+
_device_address (str): The address of the device in the network.
|
|
33
|
+
_database_address (str): The address of the InfluxDB instance.
|
|
34
|
+
_sensors (dict): Sensors attached to the device.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_influxdb_host: str # host of the influxdb instance
|
|
38
|
+
_influxdb_port: int # port of the influxdb instance
|
|
39
|
+
_influxdb_token: str # token to authenticate with influxdb
|
|
40
|
+
_influxdb_organization: str # organization to use within influxdb
|
|
41
|
+
_influxdb_bucket: str # bucket to save the data into
|
|
42
|
+
|
|
43
|
+
_dev_ip: str # ip of the device sending the data
|
|
44
|
+
_dev_port: int # port of the device sending the data
|
|
45
|
+
_dev_handle: str # handle to access the data
|
|
46
|
+
|
|
47
|
+
_dev_url: str # address of the device in the network
|
|
48
|
+
_db_url: str # address of the influxdb instance
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self, host, port, token, org, bucket, sensors, dev_ip, dev_port, handle=""
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
Initialize the fetcher with the required information.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
host (str): Host of the InfluxDB instance.
|
|
58
|
+
port (int): Port of the InfluxDB instance.
|
|
59
|
+
token (str): Token to authenticate with InfluxDB.
|
|
60
|
+
org (str): Organization to use within InfluxDB.
|
|
61
|
+
bucket (str): Bucket within InfluxDB where the data will be stored.
|
|
62
|
+
dev_ip (str):IP address of device providing sensor readings.
|
|
63
|
+
dev_port (str): Port of the device providing the readings.
|
|
64
|
+
handle (str): Http handle to access the data ("" by default).
|
|
65
|
+
sensors (dict): Which sensors device has and what do they measure.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# InfluxDB authentication data
|
|
69
|
+
self._influxdb_host = host
|
|
70
|
+
self._influxdb_port = port
|
|
71
|
+
self._influxdb_token = token
|
|
72
|
+
self._influxdb_organization = org
|
|
73
|
+
self._influxdb_bucket = bucket
|
|
74
|
+
|
|
75
|
+
# device identification
|
|
76
|
+
self._dev_ip = dev_ip
|
|
77
|
+
self._dev_port = dev_port
|
|
78
|
+
self._dev_handle = handle
|
|
79
|
+
|
|
80
|
+
# device and database URLs
|
|
81
|
+
self._dev_url = f"http://{self._dev_ip}:{self._dev_port}/{self._dev_handle}"
|
|
82
|
+
self._db_url = f"http://{self._influxdb_host}:{self._influxdb_port}"
|
|
83
|
+
|
|
84
|
+
self._sensors = sensors
|
|
85
|
+
|
|
86
|
+
def _get_reads(self, data) -> dict[str, float]:
|
|
87
|
+
"""
|
|
88
|
+
Based on sensors specified in sensors attribute fill the fields
|
|
89
|
+
with appropriate key-value pairs for InfluxDB storage.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
data (dict): The sensor readings to parse.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
fields = {}
|
|
96
|
+
for sensor in self._sensors:
|
|
97
|
+
for param in self._sensors[sensor]:
|
|
98
|
+
fields[param] = float(data["nodemcu"][sensor][param])
|
|
99
|
+
return fields
|
|
100
|
+
|
|
101
|
+
def _parse_into_records(self, data, device_name="nodemcu"):
|
|
102
|
+
"""
|
|
103
|
+
Parse raw json file into records for InfluxDB.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
data (dict): The sensor readings to parse.
|
|
107
|
+
device_name (str): The name of the device (default is 'nodemcu').
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
records = {
|
|
111
|
+
"measurement": "sensor_data",
|
|
112
|
+
"tags": {"device": device_name},
|
|
113
|
+
"timestamp": str(datetime.datetime.now()),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
records["fields"] = self._get_reads(data)
|
|
117
|
+
return records
|
|
118
|
+
|
|
119
|
+
async def _write_to_db(self, client, record):
|
|
120
|
+
"""
|
|
121
|
+
Write the sensor readings to InfluxDB.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
client (InfluxDBClientAsync): The InfluxDB client to write to.
|
|
125
|
+
records (dict): The sensor readings as records for InfluxDB.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
print("<.> writing new read into database...")
|
|
129
|
+
write_api = client.write_api()
|
|
130
|
+
point = Point.from_dict(record, write_precision="ns")
|
|
131
|
+
await write_api.write(
|
|
132
|
+
bucket=self._influxdb_bucket, org=self._influxdb_organization, record=point
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def _store_sensor_readings(self, record):
|
|
136
|
+
"""
|
|
137
|
+
Store sensor readings within InfluxDB.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
records (dict): The sensor readings in the form of InfluxDB records.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
async with InfluxDBClientAsync(
|
|
144
|
+
url=self._db_url,
|
|
145
|
+
token=self._influxdb_token,
|
|
146
|
+
org=self._influxdb_organization,
|
|
147
|
+
) as client:
|
|
148
|
+
await self._write_to_db(client, record)
|
|
149
|
+
|
|
150
|
+
async def _request_sensor_readings(self, session):
|
|
151
|
+
"""
|
|
152
|
+
Fetch the sensor readings from the device via http request.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
session: The aiohttp session to use for the request.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
async with session.get(self._dev_url) as response:
|
|
159
|
+
if response.status != 200:
|
|
160
|
+
print(f"Error fetching data: {response.status}")
|
|
161
|
+
else:
|
|
162
|
+
read = await response.json()
|
|
163
|
+
return read
|
|
164
|
+
|
|
165
|
+
async def _request_and_store(self):
|
|
166
|
+
"""
|
|
167
|
+
Request sensor reading via aiohttp and store them in InfluxDB.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
async with aiohttp.ClientSession() as session:
|
|
171
|
+
json = await self._request_sensor_readings(session)
|
|
172
|
+
await self._store_sensor_readings(self._parse_into_records(json))
|
|
173
|
+
|
|
174
|
+
async def _fetching_loop(self):
|
|
175
|
+
"""
|
|
176
|
+
Main fetcher loop to request and store sensor readings.
|
|
177
|
+
|
|
178
|
+
Loop is infinite and receives readings every second, meant to run in
|
|
179
|
+
the background.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
while True:
|
|
183
|
+
await asyncio.sleep(1)
|
|
184
|
+
await self._request_and_store()
|
|
185
|
+
|
|
186
|
+
async def schedule_fetcher(self):
|
|
187
|
+
"""
|
|
188
|
+
Create a task group managing the fetching loop.
|
|
189
|
+
|
|
190
|
+
Tested using asyncio.run()
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
async with asyncio.TaskGroup() as tg:
|
|
194
|
+
await tg.create_task(self._fetching_loop())
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interface that control both fetching, writing and querying from the database.
|
|
3
|
+
|
|
4
|
+
Author: Piotr Krzysztof Lis - github.com/straightchlorine
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import multiprocessing
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from ahttpdc.reads.fetch.async_fetch import AsyncReadFetcher
|
|
13
|
+
from ahttpdc.reads.query.async_query import AsyncQuery
|
|
14
|
+
|
|
15
|
+
__all__ = ["DatabaseInterface"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DatabaseInterface:
|
|
19
|
+
"""
|
|
20
|
+
Interface to control fetching, writing and querying from the database.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
_influxdb_host (str): The host of the InfluxDB instance.
|
|
24
|
+
_influxdb_port (int): The port of the InfluxDB instance.
|
|
25
|
+
_influxdb_token (str): The token to authenticate with InfluxDB.
|
|
26
|
+
_influxdb_organization (str): The organization to use within InfluxDB.
|
|
27
|
+
_influxdb_bucket (str): Bucket within InfluxDB where the data will be stored.
|
|
28
|
+
_dev_ip (str): The IP address of device providing sensor readings.
|
|
29
|
+
_dev_port (str): The port of the device providing the readings.
|
|
30
|
+
_dev_handle (str): The http handle to access the data.
|
|
31
|
+
_dev_url (str): The address of the device in the network.
|
|
32
|
+
_db_url (str): The address of the influxdb instance.
|
|
33
|
+
sensors (dict): The sensors and their parameters to read.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
_influxdb_host: str # host of the influxdb instance
|
|
37
|
+
_influxdb_port: int # port of the influxdb instance
|
|
38
|
+
_influxdb_token: str # token to authenticate with influxdb
|
|
39
|
+
_influxdb_organization: str # organization to use within influxdb
|
|
40
|
+
_influxdb_bucket: str # bucket to save the data into
|
|
41
|
+
|
|
42
|
+
_dev_ip: str # ip of the device sending the data
|
|
43
|
+
_dev_port: int # port of the device sending the data
|
|
44
|
+
_dev_handle: str # handle to access the data
|
|
45
|
+
|
|
46
|
+
_dev_url: str # address of the device in the network
|
|
47
|
+
_db_url: str # address of the influxdb instance
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self, host, port, token, org, bucket, sensors, dev_ip, dev_port, handle=""
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Initialize the fetcher with the required information.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
host (str): The host of the InfluxDB instance.
|
|
57
|
+
port (int): The port of the InfluxDB instance.
|
|
58
|
+
token (str): The token to authenticate with InfluxDB.
|
|
59
|
+
org (str): The organization to use within InfluxDB.
|
|
60
|
+
bucket (str): Bucket within InfluxDB where the data will be stored.
|
|
61
|
+
dev_ip (str): The IP address of device providing sensor readings.
|
|
62
|
+
dev_port (str): The port of the device providing the readings.
|
|
63
|
+
handle (str): The http handle to access the data ("" by default).
|
|
64
|
+
sensors (dict): The sensors and their parameters to read.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
self._influxdb_host = host
|
|
68
|
+
self._influxdb_port = port
|
|
69
|
+
self._influxdb_token = token
|
|
70
|
+
self._influxdb_organization = org
|
|
71
|
+
self._influxdb_bucket = bucket
|
|
72
|
+
|
|
73
|
+
self._dev_ip = dev_ip
|
|
74
|
+
self._dev_port = dev_port
|
|
75
|
+
self._dev_handle = handle
|
|
76
|
+
|
|
77
|
+
self._dev_url = f"http://{self._dev_ip}:{self._dev_port}/{self._dev_handle}"
|
|
78
|
+
self._db_url = f"http://{self._influxdb_host}:{self._influxdb_port}"
|
|
79
|
+
|
|
80
|
+
self.sensors = sensors
|
|
81
|
+
|
|
82
|
+
self._fetcher = AsyncReadFetcher(
|
|
83
|
+
self._influxdb_host,
|
|
84
|
+
self._influxdb_port,
|
|
85
|
+
self._influxdb_token,
|
|
86
|
+
self._influxdb_organization,
|
|
87
|
+
self._influxdb_bucket,
|
|
88
|
+
self.sensors,
|
|
89
|
+
self._dev_ip,
|
|
90
|
+
self._dev_port,
|
|
91
|
+
self._dev_handle,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
self.query_interface = AsyncQuery(
|
|
95
|
+
self._influxdb_host,
|
|
96
|
+
self._influxdb_port,
|
|
97
|
+
self._influxdb_token,
|
|
98
|
+
self._influxdb_organization,
|
|
99
|
+
self._influxdb_bucket,
|
|
100
|
+
self.sensors,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def enable_fetching(self):
|
|
104
|
+
"""
|
|
105
|
+
Enable fetching from the device specified by dev_ip, dev_port and handle.
|
|
106
|
+
|
|
107
|
+
Starts the fetching task in the background, thus should be invoked last
|
|
108
|
+
in order to avoid blocking the main thread.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def _start_fetching():
|
|
112
|
+
asyncio.run(self._fetcher.schedule_fetcher())
|
|
113
|
+
|
|
114
|
+
fetching_process = multiprocessing.Process(
|
|
115
|
+
target=_start_fetching, name="asyncfetcher"
|
|
116
|
+
)
|
|
117
|
+
fetching_process.start()
|
|
118
|
+
|
|
119
|
+
async def query_latest(self) -> pd.DataFrame:
|
|
120
|
+
"""
|
|
121
|
+
Query the latest measurement from the InfluxDB.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
pd.DataFrame: The latest measurement.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
print("<.> querying latest measurement...")
|
|
128
|
+
query_task = asyncio.create_task(self.query_interface.latest())
|
|
129
|
+
await query_task
|
|
130
|
+
result = query_task.result()
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
async def query_historical(self, start: str, end: str) -> pd.DataFrame:
|
|
134
|
+
"""
|
|
135
|
+
Query historical data from the database.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
start (str): Start time of the query (e.g., '2024-01-01T00:00:00Z').
|
|
139
|
+
end (str): End time of the query (e.g., '2024-01-02T00:00:00Z').
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
pd.DataFrame: Historical data within the specified time range.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
print("<.> querying historical data...")
|
|
146
|
+
query_task = asyncio.create_task(
|
|
147
|
+
self.query_interface.historical_data(start, end)
|
|
148
|
+
)
|
|
149
|
+
await query_task
|
|
150
|
+
result = query_task.result()
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
async def query(self, query: str) -> pd.DataFrame:
|
|
154
|
+
"""
|
|
155
|
+
Perform a custom query on the database.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
query (str): The InfluxDB query to execute.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
pd.DataFrame: The result of the custom query.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
print("<.> custom query...")
|
|
165
|
+
query_task = asyncio.create_task(self.query_interface.query(query))
|
|
166
|
+
await query_task
|
|
167
|
+
result = query_task.result()
|
|
168
|
+
return result
|
|
File without changes
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interface for passing queries to the database.
|
|
3
|
+
|
|
4
|
+
Author: Piotr Krzysztof Lis - github.com/straightchlorine
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
|
|
9
|
+
from influxdb_client.client.exceptions import InfluxDBError
|
|
10
|
+
from influxdb_client.client.influxdb_client_async import InfluxDBClientAsync
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
__all__ = ["AsyncQuery"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AsyncQuery:
|
|
17
|
+
"""
|
|
18
|
+
Interface to query the InfluxDB.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
_influxdb_host (str): The host of the InfluxDB instance.
|
|
22
|
+
_influxdb_port (int): The port of the InfluxDB instance.
|
|
23
|
+
_influxdb_token (str): The token to authenticate with InfluxDB.
|
|
24
|
+
_influxdb_organization (str): The organization to use within InfluxDB.
|
|
25
|
+
_influxdb_bucket (str): Bucket within InfluxDB where the data will be stored.
|
|
26
|
+
_db_url (str): The URL of the InfluxDB instance.
|
|
27
|
+
sensors_and_params (dict): The sensors and their parameters to read.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
_influxdb_host: str # host of the influxdb instance
|
|
31
|
+
_influxdb_port: int # port of the influxdb instance
|
|
32
|
+
_influxdb_token: str # token to authenticate with influxdb
|
|
33
|
+
_influxdb_organization: str # organization to use within influxdb
|
|
34
|
+
_influxdb_bucket: str # bucket to save the data into
|
|
35
|
+
|
|
36
|
+
_db_url: str # address of the influxdb instance
|
|
37
|
+
|
|
38
|
+
sensors: dict # sensors and their parameters to read
|
|
39
|
+
|
|
40
|
+
def __init__(self, host, port, token, org, bucket, sensors):
|
|
41
|
+
"""
|
|
42
|
+
Initialize the fetcher with the required information.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
host (str): The host of the InfluxDB instance.
|
|
46
|
+
port (int): The port of the InfluxDB instance.
|
|
47
|
+
token (str): The token to authenticate with InfluxDB.
|
|
48
|
+
org (str): The organization to use within InfluxDB.
|
|
49
|
+
bucket (str): Bucket within InfluxDB where the data will be stored.
|
|
50
|
+
sensors (dict): The sensors and their parameters to read.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
self._influxdb_host = host
|
|
54
|
+
self._influxdb_port = port
|
|
55
|
+
self._influxdb_token = token
|
|
56
|
+
self._influxdb_organization = org
|
|
57
|
+
self._influxdb_bucket = bucket
|
|
58
|
+
|
|
59
|
+
self._db_url = f"http://{self._influxdb_host}:{self._influxdb_port}"
|
|
60
|
+
|
|
61
|
+
self.sensors = sensors
|
|
62
|
+
|
|
63
|
+
async def _get_InfluxDB_client(self) -> InfluxDBClientAsync:
|
|
64
|
+
"""Returns an InfluxDB client."""
|
|
65
|
+
return InfluxDBClientAsync(
|
|
66
|
+
url=self._db_url,
|
|
67
|
+
token=self._influxdb_token,
|
|
68
|
+
org=self._influxdb_organization,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _convert_to_local_time(self, timestamps):
|
|
72
|
+
"""
|
|
73
|
+
Convert a collection of UTC timestamps to local time.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
timestamps (list): A list of UTC timestamps.
|
|
77
|
+
Returns:
|
|
78
|
+
list: A list of timestamps in local time.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
local_timestamps = []
|
|
82
|
+
|
|
83
|
+
# get the local offset from UTC
|
|
84
|
+
local_offset = timedelta(
|
|
85
|
+
seconds=datetime.now().astimezone().utcoffset().total_seconds()
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# add the offset to the timestamps
|
|
89
|
+
for timestamp in timestamps:
|
|
90
|
+
utc_time = pd.to_datetime(timestamp)
|
|
91
|
+
local_time = utc_time + local_offset
|
|
92
|
+
local_timestamps.append(local_time)
|
|
93
|
+
|
|
94
|
+
return local_timestamps
|
|
95
|
+
|
|
96
|
+
def _into_dataframe(self, tables) -> pd.DataFrame:
|
|
97
|
+
"""
|
|
98
|
+
Turns the tables into a pandas DataFrame.
|
|
99
|
+
Args:
|
|
100
|
+
tables (list): The tables to turn into a DataFrame.
|
|
101
|
+
Returns:
|
|
102
|
+
pd.DataFrame: procured measurements as a DataFrame.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
read: dict = {}
|
|
106
|
+
timestamps = set()
|
|
107
|
+
|
|
108
|
+
# unpacking the table
|
|
109
|
+
for table in tables:
|
|
110
|
+
for record in table.records:
|
|
111
|
+
# get the measurements
|
|
112
|
+
parameter = record.get_field()
|
|
113
|
+
measurement = record.get_value()
|
|
114
|
+
timestamps.add(record.get_time())
|
|
115
|
+
|
|
116
|
+
# ensure every parameter is present in the dict
|
|
117
|
+
if parameter not in read:
|
|
118
|
+
read[parameter] = []
|
|
119
|
+
read[parameter].append(float(measurement))
|
|
120
|
+
|
|
121
|
+
# convert timestamps to local time
|
|
122
|
+
local_timestamps = self._convert_to_local_time(timestamps)
|
|
123
|
+
|
|
124
|
+
# if there is no time key, create one
|
|
125
|
+
if "time" not in read:
|
|
126
|
+
read["time"] = []
|
|
127
|
+
|
|
128
|
+
# add the timestamps to the data dict
|
|
129
|
+
for timestamp in local_timestamps:
|
|
130
|
+
read["time"].append(pd.to_datetime(timestamp))
|
|
131
|
+
|
|
132
|
+
# return the data as a DataFrame
|
|
133
|
+
return pd.DataFrame(read)
|
|
134
|
+
|
|
135
|
+
async def latest(self) -> pd.DataFrame:
|
|
136
|
+
"""
|
|
137
|
+
Query the database for the latest measurement.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
pd.DataFrame: The latest measurement of every parameter.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
# get the connection to the database via query api
|
|
144
|
+
client = await self._get_InfluxDB_client()
|
|
145
|
+
query_api = client.query_api()
|
|
146
|
+
|
|
147
|
+
# query the latest measurement
|
|
148
|
+
query = f'from(bucket:"{self._influxdb_bucket}") |> range(start: -1h) |> last()'
|
|
149
|
+
|
|
150
|
+
tables = None
|
|
151
|
+
try:
|
|
152
|
+
tables = await query_api.query(query)
|
|
153
|
+
except InfluxDBError as e:
|
|
154
|
+
print(f"Exception caught while querying the database:\n\n {e.message}")
|
|
155
|
+
|
|
156
|
+
# close the connection
|
|
157
|
+
await client.close()
|
|
158
|
+
|
|
159
|
+
# turn the tables into a DataFrame and return it
|
|
160
|
+
if tables is not None:
|
|
161
|
+
return self._into_dataframe(tables)
|
|
162
|
+
else:
|
|
163
|
+
return pd.DataFrame()
|
|
164
|
+
|
|
165
|
+
async def historical_data(self, start: str, end: str) -> pd.DataFrame:
|
|
166
|
+
"""
|
|
167
|
+
Query historical data from the database.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
start (str): Start time of the query (e.g., '2024-01-01T00:00:00Z').
|
|
171
|
+
end (str): End time of the query (e.g., '2024-01-02T00:00:00Z').
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
pd.DataFrame: Historical data within the specified time range.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
# get the connection to the database via query api
|
|
178
|
+
client = await self._get_InfluxDB_client()
|
|
179
|
+
query_api = client.query_api()
|
|
180
|
+
|
|
181
|
+
# query the data from specified start and finish
|
|
182
|
+
query = f'from(bucket:"{self._influxdb_bucket}") |> range(start: {start}, stop: {end})'
|
|
183
|
+
|
|
184
|
+
tables = None
|
|
185
|
+
try:
|
|
186
|
+
tables = await query_api.query(query)
|
|
187
|
+
except InfluxDBError as e:
|
|
188
|
+
print(f"Exception caught while querying the database:\n\n {e.message}")
|
|
189
|
+
|
|
190
|
+
await client.close()
|
|
191
|
+
|
|
192
|
+
if tables is not None:
|
|
193
|
+
return self._into_dataframe(tables)
|
|
194
|
+
else:
|
|
195
|
+
return pd.DataFrame()
|
|
196
|
+
|
|
197
|
+
async def query(self, query: str) -> pd.DataFrame:
|
|
198
|
+
"""
|
|
199
|
+
Perform a custom query on the database.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
query (str): The InfluxDB query to execute.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
pd.DataFrame: The result of the custom query.
|
|
206
|
+
"""
|
|
207
|
+
client = await self._get_InfluxDB_client()
|
|
208
|
+
query_api = client.query_api()
|
|
209
|
+
|
|
210
|
+
tables = None
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
tables = await query_api.query(query)
|
|
214
|
+
except InfluxDBError as e:
|
|
215
|
+
print(f"Exception caught while querying the database:\n\n {e.message}")
|
|
216
|
+
|
|
217
|
+
await client.close()
|
|
218
|
+
|
|
219
|
+
if tables is not None:
|
|
220
|
+
return self._into_dataframe(tables)
|
|
221
|
+
else:
|
|
222
|
+
return pd.DataFrame()
|