redis beta 1
This commit is contained in:
1 parent
63102b7740
commit
b23b52ed2e
3 files changed
+33
-26
No files matched your search
@@ -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
@@ -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
@@ -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"))
|
||||||
|
|||||||
Reference in new issue
Block a user