temperature-sensor-scanner 1.0.0__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.
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from bleak import BleakScanner # pip install bleak
|
|
5
|
+
from functools import partial
|
|
6
|
+
from atc_mi_interface import (
|
|
7
|
+
general_format,
|
|
8
|
+
atc_mi_advertising_format,
|
|
9
|
+
) # pip install atc-mi-interface
|
|
10
|
+
|
|
11
|
+
# atc-mi-interface
|
|
12
|
+
# https://github.com/pvvx/ATC_MiThermometer/tree/master/python-interface#decoding-and-encoding
|
|
13
|
+
import influxdb_client # pip install influxdb-client
|
|
14
|
+
from influxdb_client.client.write_api import SYNCHRONOUS
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
import yaml # pip install PyYAML
|
|
20
|
+
from platformdirs import user_config_dir # pip install platformdirs
|
|
21
|
+
|
|
22
|
+
APP_NAME = "temperature-sensor-scanner"
|
|
23
|
+
APP_AUTHOR = "Cementhorizon"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_config_path() -> Path:
|
|
27
|
+
"""
|
|
28
|
+
Returns the full path to config.yaml in the user config directory.
|
|
29
|
+
Example:
|
|
30
|
+
Linux: ~/.config/APP_NAME/config.yaml
|
|
31
|
+
macOS: ~/Library/Application Support/APP_NAME/config.yaml
|
|
32
|
+
Windows: C:\\Users\\<user>\\AppData\\Local\\APP_AUTHOR\\APP_NAME\\config.yaml
|
|
33
|
+
"""
|
|
34
|
+
config_dir = Path(user_config_dir(APP_NAME, APP_AUTHOR))
|
|
35
|
+
return config_dir / "config.yaml"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_config(path: Path) -> dict:
|
|
39
|
+
"""
|
|
40
|
+
Loads YAML config.
|
|
41
|
+
"""
|
|
42
|
+
if not path.exists():
|
|
43
|
+
print("Missing parsing config file", file=sys.stderr)
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
with path.open("r") as f:
|
|
48
|
+
return yaml.safe_load(f) or {}
|
|
49
|
+
except yaml.YAMLError as e:
|
|
50
|
+
print(f"Error parsing config file: {e}", file=sys.stderr)
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def gather_data(config: dict) -> list:
|
|
55
|
+
scan_results = asyncio.run(ble_coro(config))
|
|
56
|
+
|
|
57
|
+
# name = atc_mi_data.atc1441_format[0].MAC.replace(":", "")[-6:]
|
|
58
|
+
# print(f"ATC_{name} : {temperature:.1f}")
|
|
59
|
+
|
|
60
|
+
points = []
|
|
61
|
+
macs_seen = set()
|
|
62
|
+
for scan_result in scan_results:
|
|
63
|
+
if scan_result["mac"] in macs_seen:
|
|
64
|
+
continue
|
|
65
|
+
else:
|
|
66
|
+
macs_seen.add(scan_result["mac"])
|
|
67
|
+
sensor = config["sensors"][scan_result["mac"]]
|
|
68
|
+
points.append(
|
|
69
|
+
influxdb_client.Point("environment")
|
|
70
|
+
.tag("location", sensor["location"])
|
|
71
|
+
.tag("domain", sensor["domain"])
|
|
72
|
+
.tag("mac", scan_result["mac"])
|
|
73
|
+
.field("temperature", scan_result["temperature"])
|
|
74
|
+
)
|
|
75
|
+
return points
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def emit_data(config: dict, points: list) -> None:
|
|
79
|
+
bucket = config["influxdb"]["bucket"]
|
|
80
|
+
org = config["influxdb"]["org"]
|
|
81
|
+
token = config["influxdb"]["token"]
|
|
82
|
+
url = config["influxdb"]["url"]
|
|
83
|
+
|
|
84
|
+
client = influxdb_client.InfluxDBClient(url=url, token=token, org=org)
|
|
85
|
+
|
|
86
|
+
write_api = client.write_api(write_options=SYNCHRONOUS)
|
|
87
|
+
write_api.write(bucket=bucket, org=org, record=points)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def ble_coro(config):
|
|
91
|
+
count = [0]
|
|
92
|
+
stop_event = asyncio.Event()
|
|
93
|
+
results = []
|
|
94
|
+
|
|
95
|
+
def detection_callback(count, results, device, advertisement_data):
|
|
96
|
+
format_label, adv_data = atc_mi_advertising_format(advertisement_data)
|
|
97
|
+
if not adv_data:
|
|
98
|
+
return
|
|
99
|
+
mac_address = bytes.fromhex(device.address.replace(":", ""))
|
|
100
|
+
bindkey = config["sensors"][device.address.replace(":", "")]["bindkey"]
|
|
101
|
+
|
|
102
|
+
atc_mi_data = general_format.parse(
|
|
103
|
+
adv_data,
|
|
104
|
+
mac_address=mac_address,
|
|
105
|
+
bindkey=None if bindkey is None else bytes.fromhex(bindkey),
|
|
106
|
+
)
|
|
107
|
+
name = atc_mi_data.atc1441_format[0].MAC.replace(":", "")[-6:]
|
|
108
|
+
if atc_mi_data.atc1441_format[0].temperature_unit == "°C":
|
|
109
|
+
temperature = (atc_mi_data.atc1441_format[0].temperature * 1.8) + 32
|
|
110
|
+
else:
|
|
111
|
+
temperature = atc_mi_data.atc1441_format[0].temperature
|
|
112
|
+
print(f"ATC_{name} : {temperature:.1f}")
|
|
113
|
+
|
|
114
|
+
results.append({
|
|
115
|
+
"mac": atc_mi_data.atc1441_format[0].MAC.replace(":", ""),
|
|
116
|
+
"temperature": temperature,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
count[0] += 1
|
|
120
|
+
if count[0] == 8:
|
|
121
|
+
stop_event.set()
|
|
122
|
+
|
|
123
|
+
async with BleakScanner(
|
|
124
|
+
detection_callback=partial(detection_callback, count, results)
|
|
125
|
+
) as scanner:
|
|
126
|
+
await stop_event.wait()
|
|
127
|
+
return results
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def main():
|
|
131
|
+
config_path = get_config_path()
|
|
132
|
+
config = load_config(config_path)
|
|
133
|
+
points = gather_data(config)
|
|
134
|
+
emit_data(config, points)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
main()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: temperature-sensor-scanner
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A tool to collect data from temperature sensors and emit it into InfluxDB
|
|
5
|
+
Project-URL: Homepage, https://github.com/gene1wood/temperature-sensor-scanner
|
|
6
|
+
Project-URL: Repository, https://github.com/gene1wood/temperature-sensor-scanner
|
|
7
|
+
Author-email: Gene Wood <gene_wood@cementhorizon.com>
|
|
8
|
+
License: GPL-3.0-or-later
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: atc-mi-interface
|
|
11
|
+
Requires-Dist: bleak
|
|
12
|
+
Requires-Dist: influxdb-client
|
|
13
|
+
Requires-Dist: platformdirs
|
|
14
|
+
Requires-Dist: pyyaml
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# temperature-sensor-scanner
|
|
18
|
+
|
|
19
|
+
A tool to scan nearby Bluetooth Low Energy thermometers on the Telink chipset, collect the data ane emit it into
|
|
20
|
+
InfluxDB.
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
* Create an influxdb user with a command like `influx user create --org [my-org] --name [my-username] --password [my-password]`
|
|
25
|
+
* Create an influxdb token with a command like `influx auth create --org [my-org] --user [my-username] --write-bucket [my-bucket-id]`
|
|
26
|
+
* Create a `config.yaml` in `~/.config/temperature-sensor-scanner/` based on the `config.example.yaml` file
|
|
27
|
+
* Install this tool with a command like `pip install temperature-sensor-scanner`
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
Run `temperature-sensor-scanner` which will scan for nearby sensors which are emitting temperature data in their Bluetooth
|
|
32
|
+
advertising string, collect that data, and emit it into InfluxDB.
|
|
33
|
+
|
|
34
|
+
# Notes
|
|
35
|
+
|
|
36
|
+
* https://github.com/pvvx/ATC_MiThermometer/
|
|
37
|
+
* https://pvvx.github.io/ATC_MiThermometer/TelinkMiFlasher.html
|
|
38
|
+
* Launch in Chrome instead of Firefox
|
|
39
|
+
* Other tools that look interesting
|
|
40
|
+
* https://github.com/JsBergbau/MiTemperature2
|
|
41
|
+
|
|
42
|
+
## Steps to provision a new sensor
|
|
43
|
+
|
|
44
|
+
* [Flash the firmware](https://github.com/pvvx/ATC_MiThermometer/?tab=readme-ov-file#flashing-or-updating-the-firmware-ota)
|
|
45
|
+
* Click `Connect`
|
|
46
|
+
* Select the `LYWSD03MMC` Bluetooth device in the menu
|
|
47
|
+
* Click `Do Activation`
|
|
48
|
+
* Choose `Custom Firmware ATC_v56.bin`
|
|
49
|
+
* Click `Start Flashing`
|
|
50
|
+
* Wait 42 seconds for it to show update done
|
|
51
|
+
* Identify ID from Bluetooth announced name and print a label to put on the sensor
|
|
52
|
+
* Quit Chrome and start again
|
|
53
|
+
* [Configure sensor](https://github.com/pvvx/ATC_MiThermometer/?tab=readme-ov-file#configuration)
|
|
54
|
+
* Set display to Farenheight
|
|
55
|
+
* Click `Set Time`
|
|
56
|
+
* Set advertising type to `ATC1441`
|
|
57
|
+
* Set advertising interval to `10000` ( https://github.com/pvvx/ATC_MiThermometer/issues/23#issuecomment-766898945 )
|
|
58
|
+
* Set Measure interval: to 10
|
|
59
|
+
* Click `Send Config`
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
temperature_sensor_scanner/__init__.py,sha256=uf2cnbKn_pwDp81FuNfJho0TU82m6UsOG3cnwTlXmZQ,4225
|
|
2
|
+
temperature_sensor_scanner-1.0.0.dist-info/METADATA,sha256=CvSSYHNKCluHKNxa5z3k1dcLigBp-XS3Fx81OjlLJBk,2505
|
|
3
|
+
temperature_sensor_scanner-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
4
|
+
temperature_sensor_scanner-1.0.0.dist-info/entry_points.txt,sha256=-RABphDkoz7DtXP0eymic_gO7Mt8wN6VW2fmHvXL7FU,79
|
|
5
|
+
temperature_sensor_scanner-1.0.0.dist-info/RECORD,,
|