motion detection example home assistant
This commit is contained in:
parent
6f454c6190
commit
66c893b50b
@ -3,7 +3,6 @@ This example shows how to use the CSI framework to connect to a FeitCSI host and
|
||||
detect changes in the environment
|
||||
"""
|
||||
|
||||
import logging
|
||||
from queue import Queue
|
||||
|
||||
import numpy as np
|
||||
@ -16,37 +15,46 @@ from where_fi.collection.ingest import RealtimeCSIProducer
|
||||
producer = RealtimeCSIProducer()
|
||||
app = CSIApplication(producer)
|
||||
|
||||
# Store historical CSI data to compare with the current data
|
||||
# This allows us to detect sudden changes in the environment,
|
||||
# most likely to be cause by motion
|
||||
|
||||
# This is stored individually for each receiving antenna, for each subcarrier
|
||||
# Stores historical data for each receiving antenna, for each subcarrier
|
||||
historical: dict[tuple[int, int], Queue[np.complex64]] = {}
|
||||
|
||||
MAGN_THRESHOLD = 20
|
||||
PHASE_THRESHOLD = 0.5
|
||||
QUEUE_SIZE = 2000
|
||||
|
||||
|
||||
@app.on_process
|
||||
def _(antenna_data: npt.NDArray[np.complex64]) -> None:
|
||||
for antenna in range(antenna_data.shape[1]):
|
||||
for subcarrier in range(antenna_data.shape[0]):
|
||||
def _(sample: npt.NDArray[np.complex64]) -> None:
|
||||
"""
|
||||
Process the CSI data and detect changes in the environment.
|
||||
|
||||
This function is called by the framework at a fixed interval, with the latest CSI
|
||||
sample received.
|
||||
|
||||
It is used to detect changes in the environment caused by motion, by comparing each
|
||||
entry in the matrix with a moving average
|
||||
"""
|
||||
change = False
|
||||
for antenna in range(sample.shape[1]):
|
||||
for subcarrier in range(sample.shape[0]):
|
||||
# Get the current subcarrier data
|
||||
current = antenna_data[subcarrier, antenna, 0]
|
||||
current = sample[subcarrier, antenna, 0]
|
||||
|
||||
# Get the historical data for this antenna and subcarrier
|
||||
if (antenna, subcarrier) not in historical:
|
||||
historical[(antenna, subcarrier)] = Queue(maxsize=100)
|
||||
historical[(antenna, subcarrier)] = Queue(maxsize=QUEUE_SIZE)
|
||||
historical_data = historical[(antenna, subcarrier)]
|
||||
|
||||
# Calculate the average of the historical data
|
||||
mean: np.complex64 = np.mean(historical_data.queue)
|
||||
# Calculate the average of the historical data for this antenna and
|
||||
# subcarrier
|
||||
mean = np.mean(historical_data.queue)
|
||||
|
||||
# If we have enough historical data, compare it with the current data
|
||||
if historical_data.full():
|
||||
historical_data.get()
|
||||
|
||||
# Add the current data to the historical data
|
||||
# Add the current sample to the historical data
|
||||
historical_data.put(current)
|
||||
|
||||
# Compare the current data with the historical data
|
||||
@ -54,11 +62,11 @@ def _(antenna_data: npt.NDArray[np.complex64]) -> None:
|
||||
np.abs(mean - current) > MAGN_THRESHOLD
|
||||
or np.abs(np.angle(mean) - np.angle(current)) > PHASE_THRESHOLD
|
||||
):
|
||||
logging.info(
|
||||
f"Change detected on antenna {antenna}, subcarrier {subcarrier} "
|
||||
f"|{np.abs(mean - current)}| <{np.angle(mean) - np.angle(current)}>"
|
||||
)
|
||||
change = True
|
||||
if change:
|
||||
print("Motion detected!")
|
||||
else:
|
||||
print("No motion detected!")
|
||||
|
||||
|
||||
# Start the application
|
||||
app.start()
|
||||
|
||||
109
examples/motion_detector_home_assistant.py
Normal file
109
examples/motion_detector_home_assistant.py
Normal file
@ -0,0 +1,109 @@
|
||||
"""Motion Detection example
|
||||
This example shows how to use the CSI framework to connect to a FeitCSI host and
|
||||
detect changes in the environment
|
||||
|
||||
Once motion is detected, the application will log the change to Home Assistant
|
||||
through an HTTP request.
|
||||
"""
|
||||
|
||||
import os
|
||||
from queue import Queue
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import requests
|
||||
|
||||
from where_fi.application import CSIApplication
|
||||
from where_fi.collection.ingest import RealtimeCSIProducer
|
||||
|
||||
# Connect to a FeitCSI host
|
||||
producer = RealtimeCSIProducer()
|
||||
app = CSIApplication(producer)
|
||||
|
||||
|
||||
class HomeAssistantBinarySensor:
|
||||
"""
|
||||
Represents a binary sensor in Home Assistant.
|
||||
|
||||
Uses the HTTP API [1] to update the state of the sensor.
|
||||
|
||||
[1] - https://www.home-assistant.io/integrations/http/#binary-sensor
|
||||
"""
|
||||
|
||||
def __init__(self, id: str, name: str) -> None:
|
||||
self.id = id
|
||||
self.name = name
|
||||
|
||||
BASE_URL = os.getenv("HOME_ASSISTANT_URL")
|
||||
API_KEY = os.getenv("HOME_ASSISTANT_API_KEY")
|
||||
|
||||
self.url = f"{BASE_URL}/api/states/binary_sensor.{self.id}"
|
||||
self.headers = {"Authorization": f"Bearer {API_KEY}"}
|
||||
|
||||
self.state = False
|
||||
|
||||
def update(self, state: bool) -> None:
|
||||
if self.state == state:
|
||||
return
|
||||
|
||||
self.state = state
|
||||
data = {
|
||||
"state": "on" if state else "off",
|
||||
"attributes": {"friendly_name": self.name},
|
||||
}
|
||||
requests.post(self.url, json=data, headers=self.headers)
|
||||
|
||||
|
||||
# Stores historical data for each receiving antenna, for each subcarrier
|
||||
historical: dict[tuple[int, int], Queue[np.complex64]] = {}
|
||||
sensor = HomeAssistantBinarySensor("motion_detector", "Motion Detector")
|
||||
|
||||
MAGN_THRESHOLD = 20
|
||||
PHASE_THRESHOLD = 0.5
|
||||
QUEUE_SIZE = 2000
|
||||
|
||||
|
||||
@app.on_process
|
||||
def _(sample: npt.NDArray[np.complex64]) -> None:
|
||||
"""
|
||||
Process the CSI data and detect changes in the environment.
|
||||
|
||||
This function is called by the framework at a fixed interval, with the latest CSI
|
||||
sample received.
|
||||
|
||||
It is used to detect changes in the environment caused by motion, by comparing each
|
||||
entry in the matrix with a moving average
|
||||
"""
|
||||
change = False
|
||||
for antenna in range(sample.shape[1]):
|
||||
for subcarrier in range(sample.shape[0]):
|
||||
# Get the current subcarrier data
|
||||
current = sample[subcarrier, antenna, 0]
|
||||
|
||||
# Get the historical data for this antenna and subcarrier
|
||||
if (antenna, subcarrier) not in historical:
|
||||
historical[(antenna, subcarrier)] = Queue(maxsize=QUEUE_SIZE)
|
||||
historical_data = historical[(antenna, subcarrier)]
|
||||
|
||||
# Calculate the average of the historical data for this antenna and
|
||||
# subcarrier
|
||||
mean = np.mean(historical_data.queue)
|
||||
|
||||
# If we have enough historical data, compare it with the current data
|
||||
if historical_data.full():
|
||||
historical_data.get()
|
||||
|
||||
# Add the current sample to the historical data
|
||||
historical_data.put(current)
|
||||
|
||||
# Compare the current data with the historical data
|
||||
if (
|
||||
np.abs(mean - current) > MAGN_THRESHOLD
|
||||
or np.abs(np.angle(mean) - np.angle(current)) > PHASE_THRESHOLD
|
||||
):
|
||||
change = True
|
||||
|
||||
sensor.update(change)
|
||||
|
||||
|
||||
app.start()
|
||||
@ -15,6 +15,7 @@ dependencies = [
|
||||
"pydantic>=2.10.6",
|
||||
"torch",
|
||||
"grpcio>=1.70.0",
|
||||
"requests>=2.32.3",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
@ -54,4 +55,5 @@ dev = [
|
||||
"grpcio-tools>=1.70.0",
|
||||
"matplotlib-stubs>=0.1.0",
|
||||
"protoletariat>=3.3.9",
|
||||
"types-requests>=2.32.0.20250328",
|
||||
]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user