redis beta 1

This commit is contained in:
matt committed 2026-10-13 03:14:25 -07:00
1 parent 63102b7740
commit b23b52ed2e
3 files changed
+33 -26

No files matched your search

-3
View File
@@ -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. 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 * Friendly Names for Sensors
* There will be a variable added to the `config.py` file to pre-configure friendly names for specific MAC addresses. * 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* * 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/ ble-scanner/
├─ app.py # Flask + Scheduler + API ├─ app.py # Flask + Scheduler + API
├─ ble.py # BLE helper functions to read temp+humidity data ├─ 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 ├─ scanner.py # Async BLE scan + cache logic
├─ config.py # Shared constants (prefix, interval, etc.) ├─ config.py # Shared constants (prefix, interval, etc.)
├─ requirements.txt # Python VENV requirements ├─ requirements.txt # Python VENV requirements
+1 -2
View File
@@ -6,8 +6,7 @@ from config import *
#################################################### ####################################################
### Redis Functions ### 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): def update_redis(data):
# push data to redis # push data to redis
+32 -21
View File
@@ -20,44 +20,60 @@ _sensors_cache = []
MI_SERVICE_UUID = "ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6" MI_SERVICE_UUID = "ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6"
TEMP_CHAR_UUID = "ebe0ccc1-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: def _is_match(mac: str, name: str) -> bool:
"""Return True if the MAC starts with the configured prefix and is correct model.""" """Return True if the MAC starts with the configured prefix and is correct model."""
if name == "LYWSD03MMC": if name == "LYWSD03MMC":
return mac.upper().startswith(MAC_PREFIX.upper()) return mac.upper().startswith(MAC_PREFIX.upper())
# this function scans for MACs
async def _scan_once() -> Iterable[str]: async def _scan_once() -> Iterable[str]:
"""Do one scan cycle and return the matching MACs.""" """Do one scan cycle and return the matching MACs."""
devices = await BleakScanner.discover(timeout=5.0) # 5s scan devices = await BleakScanner.discover(timeout=5.0) # 5s scan
return [dev.address for dev in devices if _is_match(dev.address, dev.name)] 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: def update_cache() -> None:
""" """
Called by the scheduler (in a separate thread). It runs a tiny Called by the scheduler (in a separate thread). It runs a tiny
asyncio event loop to call the Bleak scan, then updates the cache. asyncio event loop to call the Bleak scan, then updates the cache.
""" """
# Run the async scan in a fresh loop; Bleak requires this. # Run the async scan in a fresh loop; Bleak requires this.
# Gets the MACs
new_macs = scan_macs() new_macs = scan_macs()
print(f"[Scanner] Updated cache, {len(_mac_cache)} unique MAC(s).") print(f"[Scanner] Updated cache, {len(_mac_cache)} unique MAC(s).")
print("Updating data for current MACs...") print("Updating data for current MACs...")
# now that we have MACs, let's get sensor data
sensor_data = None sensor_data = None
try: try:
#asyncio.run(update_data())
sensor_data = sensor_data_query(new_macs) sensor_data = sensor_data_query(new_macs)
print(sensor_data) print(sensor_data)
except Exception as e: except Exception as e:
print(f"Aww fuck, no data :(- {e}") print(f"Aww fuck, no data :(- {e}")
# now we update the data in the sensor objects
for sensor_datum in sensor_data: for sensor_datum in sensor_data:
print(f"MAC - {sensor_datum['name']} | temp - {sensor_datum['temp']} | humidity - {sensor_datum['humidity']}") print(f"MAC - {sensor_datum['name']} | temp - {sensor_datum['temp']} | humidity - {sensor_datum['humidity']}")
for sensor in _sensors_cache: for sensor in _sensors_cache:
if sensor.name == sensor_datum['name']: if sensor.name == sensor_datum['name']:
sensor.add_or_update(key="temp", value=sensor_datum['temp']) sensor.add_or_update(key="temp", value=sensor_datum['temp'])
sensor.add_or_update(key="humidity", value=sensor_datum['humidity']) sensor.add_or_update(key="humidity", value=sensor_datum['humidity'])
# if redis is enabled, push full details to the redis server
if enable_redis: if enable_redis:
update_redis(get_sensor_details()) update_redis(get_sensor_details())
# this helper function scans for MACs
def scan_macs(): def scan_macs():
new_macs = asyncio.run(_scan_once()) new_macs = asyncio.run(_scan_once())
with _lock: with _lock:
@@ -70,58 +86,53 @@ def scan_macs():
_mac_cache.update(new_macs) _mac_cache.update(new_macs)
return new_macs return new_macs
def update_data(): # return just the MACs
for mac in set(_mac_cache):
print(f"Test - {mac}")
result = asyncio.run(read_data(mac))
def get_cache_snapshot() -> Set[str]: def get_cache_snapshot() -> Set[str]:
"""Return a thread-safe snapshot of the current cache.""" """Return a thread-safe snapshot of the current cache."""
with _lock: with _lock:
return set(_mac_cache) 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 = [] sensor_list = []
for sensor in _sensors_cache: 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}") print(f"Summary - {sensor.name}")
new_sensor = { new_sensor = {
"name": sensor.name, "name": sensor.name,
# "first_seen": ts_to_pst_string(sensor.first_seen), "first_seen": first_seen,
# "last_seen": ts_to_pst_string(sensor.last_seen), "last_seen": last_seen,
"first_seen": sensor.first_seen,
"last_seen": sensor.last_seen,
"comment": sensor.comment, "comment": sensor.comment,
"data": sensor.to_dict() "data": sensor.to_dict()
} }
sensor_list.append(new_sensor) sensor_list.append(new_sensor)
result = { result = {
"sensors": sensor_list "sensors": sensor_list
} }
# print(result)
return result return result
# return just sensor data without timestamps
def get_sensor_summary(): def get_sensor_summary():
sensor_list = [] sensor_list = []
for sensor in _sensors_cache: for sensor in _sensors_cache:
print(f"Summary - {sensor.name}") print(f"Summary - {sensor.name}")
new_sensor = { new_sensor = {
"name": sensor.name, "name": sensor.name,
"comment": sensor.comment, "comment": sensor.comment,
"data": sensor.to_dict(simple_return=True) "data": sensor.to_dict(simple_return=True)
} }
sensor_list.append(new_sensor) sensor_list.append(new_sensor)
result = { result = {
"sensors": sensor_list "sensors": sensor_list
} }
# print(result)
return result return result
# helper function for ts int to string
def ts_to_pst_string(ts_str): def ts_to_pst_string(ts_str):
utc_dt = datetime.fromtimestamp(ts_str, tz=timezone.utc) utc_dt = datetime.fromtimestamp(ts_str, tz=timezone.utc)
pacific_dt = utc_dt.astimezone(ZoneInfo("America/Los_Angeles")) pacific_dt = utc_dt.astimezone(ZoneInfo("America/Los_Angeles"))