redis beta 1
This commit is contained in:
1 parent
63102b7740
commit
b23b52ed2e
3 files changed
+34
-27
No files matched your search
@@ -2,7 +2,7 @@
|
||||
|
||||
## Summary
|
||||
|
||||
This is an API for monitoring Temperature and Humidity data for Xiaomii LYWSD03MMC BLE sensors. The app will scan for any of these devices by looking for the Bluetooth MAC octet and then filtering by model. Once it has a list of MACs, it will then attempt to read the Temp/Humidity data for each MAC and record it. This data can be interacted with through an API.
|
||||
This is an API for monitoring Temperature and Humidity data for Xiaomii LYWSD03MMC BLE sensors. The app will scan for any of these devices by looking for the Bluetooth MAC octet and then filtering by model. Once it has a list of MACs, it will then attempt to read the Temp/Humidity data for each MAC and record it. This data can be interacted with through an API.
|
||||
|
||||
Some product info can be found here at my Knowledgebase:
|
||||
|
||||
@@ -12,8 +12,6 @@ https://kb.matt-cloud.com/books/07-saved-articles/page/xiaomii-temperaturehumidi
|
||||
|
||||
Aside from a REST API, there are a few more features which need to be added to this app.
|
||||
|
||||
* REDIS function for Node.js Websocket support
|
||||
* In the end this will drive a web dashboard, and I want to be able to view live data on this dashboard
|
||||
* Friendly Names for Sensors
|
||||
* There will be a variable added to the `config.py` file to pre-configure friendly names for specific MAC addresses.
|
||||
* For example, *Living Room* or *Main Bedroom*
|
||||
@@ -182,7 +180,6 @@ The Python code is split across multiple files with different files having code
|
||||
ble-scanner/
|
||||
├─ app.py # Flask + Scheduler + API
|
||||
├─ ble.py # BLE helper functions to read temp+humidity data
|
||||
├─ ble-redis.py # Redis functions for Websocket support
|
||||
├─ scanner.py # Async BLE scan + cache logic
|
||||
├─ config.py # Shared constants (prefix, interval, etc.)
|
||||
├─ requirements.txt # Python VENV requirements
|
||||
|
||||
+1
-2
@@ -6,8 +6,7 @@ from config import *
|
||||
####################################################
|
||||
### Redis Functions
|
||||
####################################################
|
||||
if enable_redis:
|
||||
r = redis.Redis(host=bind_ip, port=redis_port)
|
||||
r = redis.Redis(host=bind_ip, port=redis_port)
|
||||
|
||||
def update_redis(data):
|
||||
# push data to redis
|
||||
|
||||
+32
-21
@@ -20,44 +20,60 @@ _sensors_cache = []
|
||||
MI_SERVICE_UUID = "ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6"
|
||||
TEMP_CHAR_UUID = "ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6"
|
||||
|
||||
# Redis Functions
|
||||
r = redis.Redis(host=bind_ip, port=redis_port)
|
||||
|
||||
def update_redis(data):
|
||||
# push data to redis
|
||||
# Publish to the Redis channel that the WS server is listening on
|
||||
r.publish('temp_hum', json.dumps(data))
|
||||
if debug_output:
|
||||
print("=== Stats Redis Update ===")
|
||||
print(json.dumps(data, indent=2))
|
||||
return True
|
||||
|
||||
# this function filters MACs
|
||||
def _is_match(mac: str, name: str) -> bool:
|
||||
"""Return True if the MAC starts with the configured prefix and is correct model."""
|
||||
if name == "LYWSD03MMC":
|
||||
return mac.upper().startswith(MAC_PREFIX.upper())
|
||||
|
||||
|
||||
# this function scans for MACs
|
||||
async def _scan_once() -> Iterable[str]:
|
||||
"""Do one scan cycle and return the matching MACs."""
|
||||
devices = await BleakScanner.discover(timeout=5.0) # 5s scan
|
||||
return [dev.address for dev in devices if _is_match(dev.address, dev.name)]
|
||||
|
||||
# this very important function updates the MAC cache and the sensor objects
|
||||
def update_cache() -> None:
|
||||
"""
|
||||
Called by the scheduler (in a separate thread). It runs a tiny
|
||||
asyncio event loop to call the Bleak scan, then updates the cache.
|
||||
"""
|
||||
# Run the async scan in a fresh loop; Bleak requires this.
|
||||
# Gets the MACs
|
||||
new_macs = scan_macs()
|
||||
print(f"[Scanner] Updated cache, {len(_mac_cache)} unique MAC(s).")
|
||||
print("Updating data for current MACs...")
|
||||
# now that we have MACs, let's get sensor data
|
||||
sensor_data = None
|
||||
try:
|
||||
#asyncio.run(update_data())
|
||||
sensor_data = sensor_data_query(new_macs)
|
||||
print(sensor_data)
|
||||
except Exception as e:
|
||||
print(f"Aww fuck, no data :(- {e}")
|
||||
|
||||
# now we update the data in the sensor objects
|
||||
for sensor_datum in sensor_data:
|
||||
print(f"MAC - {sensor_datum['name']} | temp - {sensor_datum['temp']} | humidity - {sensor_datum['humidity']}")
|
||||
for sensor in _sensors_cache:
|
||||
if sensor.name == sensor_datum['name']:
|
||||
sensor.add_or_update(key="temp", value=sensor_datum['temp'])
|
||||
sensor.add_or_update(key="humidity", value=sensor_datum['humidity'])
|
||||
|
||||
# if redis is enabled, push full details to the redis server
|
||||
if enable_redis:
|
||||
update_redis(get_sensor_details())
|
||||
|
||||
# this helper function scans for MACs
|
||||
def scan_macs():
|
||||
new_macs = asyncio.run(_scan_once())
|
||||
with _lock:
|
||||
@@ -70,58 +86,53 @@ def scan_macs():
|
||||
_mac_cache.update(new_macs)
|
||||
return new_macs
|
||||
|
||||
def update_data():
|
||||
for mac in set(_mac_cache):
|
||||
print(f"Test - {mac}")
|
||||
result = asyncio.run(read_data(mac))
|
||||
|
||||
# return just the MACs
|
||||
def get_cache_snapshot() -> Set[str]:
|
||||
"""Return a thread-safe snapshot of the current cache."""
|
||||
with _lock:
|
||||
return set(_mac_cache)
|
||||
|
||||
def get_sensor_details():
|
||||
# return sensor data and timestamps, can return int or string
|
||||
def get_sensor_details(friendly_date = False):
|
||||
sensor_list = []
|
||||
|
||||
for sensor in _sensors_cache:
|
||||
first_seen = sensor.first_seen
|
||||
last_seen = sensor.last_seen
|
||||
if friendly_date:
|
||||
first_seen = ts_to_pst_string(sensor.first_seen)
|
||||
last_seen = ts_to_pst_string(sensor.last_seen)
|
||||
print(f"Summary - {sensor.name}")
|
||||
new_sensor = {
|
||||
"name": sensor.name,
|
||||
# "first_seen": ts_to_pst_string(sensor.first_seen),
|
||||
# "last_seen": ts_to_pst_string(sensor.last_seen),
|
||||
"first_seen": sensor.first_seen,
|
||||
"last_seen": sensor.last_seen,
|
||||
"first_seen": first_seen,
|
||||
"last_seen": last_seen,
|
||||
"comment": sensor.comment,
|
||||
"data": sensor.to_dict()
|
||||
|
||||
}
|
||||
sensor_list.append(new_sensor)
|
||||
|
||||
result = {
|
||||
"sensors": sensor_list
|
||||
}
|
||||
# print(result)
|
||||
return result
|
||||
|
||||
# return just sensor data without timestamps
|
||||
def get_sensor_summary():
|
||||
sensor_list = []
|
||||
|
||||
for sensor in _sensors_cache:
|
||||
print(f"Summary - {sensor.name}")
|
||||
new_sensor = {
|
||||
"name": sensor.name,
|
||||
"comment": sensor.comment,
|
||||
"data": sensor.to_dict(simple_return=True)
|
||||
|
||||
}
|
||||
sensor_list.append(new_sensor)
|
||||
|
||||
result = {
|
||||
"sensors": sensor_list
|
||||
}
|
||||
# print(result)
|
||||
return result
|
||||
|
||||
# helper function for ts int to string
|
||||
def ts_to_pst_string(ts_str):
|
||||
utc_dt = datetime.fromtimestamp(ts_str, tz=timezone.utc)
|
||||
pacific_dt = utc_dt.astimezone(ZoneInfo("America/Los_Angeles"))
|
||||
|
||||
Reference in new issue
Block a user