124 lines
3.3 KiB
Python
124 lines
3.3 KiB
Python
"""Motion Detection example
|
|
This example shows how to use the CSI framework to connect to a FeitCSI host and
|
|
detect changes in the environment
|
|
"""
|
|
|
|
import numpy as np
|
|
import numpy.typing as npt
|
|
import requests
|
|
|
|
from where_fi.application import CSIApplication
|
|
from where_fi.collection.ingest import RealtimeCSIProducer
|
|
from where_fi.config import config
|
|
from where_fi.visualise import server as visualise
|
|
|
|
# Connect to a FeitCSI host
|
|
app = CSIApplication(visualise_raw=True)
|
|
|
|
# Register visualisations
|
|
app.register_figure(
|
|
"magn-diff",
|
|
visualise.figures.PerAntennaFigure(
|
|
[
|
|
visualise.figures.SimpleLineChart(
|
|
"Magnitude diff", "Subcarrier", "Magnitude"
|
|
),
|
|
],
|
|
[lambda x: x],
|
|
),
|
|
)
|
|
app.register_figure(
|
|
"phase-diff",
|
|
visualise.figures.PerAntennaFigure(
|
|
[
|
|
visualise.figures.SimpleLineChart("Phase diff", "Subcarrier", "Phase"),
|
|
],
|
|
[lambda x: x],
|
|
),
|
|
)
|
|
|
|
MAGN_THRESHOLD = 0.2
|
|
PHASE_THRESHOLD = 0.02
|
|
QUEUE_SIZE = 20
|
|
|
|
# Stores historical data for each receiving antenna, for each subcarrier
|
|
historical = np.zeros(
|
|
(QUEUE_SIZE, config.subcarriers, config.antennas.count, 1), dtype=np.complex64
|
|
)
|
|
sample_position = 0
|
|
|
|
|
|
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, "device_class": "motion"},
|
|
}
|
|
print(f"Updating sensor {self.name} to {data}")
|
|
requests.post(self.url, json=data, headers=self.headers)
|
|
|
|
|
|
sensor = HomeAssistantBinarySensor("motion_detector", "Room Motion Detector")
|
|
|
|
|
|
@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
|
|
"""
|
|
global sample_position
|
|
|
|
historical[sample_position] = sample
|
|
sample_position = (sample_position + 1) % QUEUE_SIZE
|
|
|
|
mean = np.mean(historical, axis=0)
|
|
magn_diff = np.abs(mean - sample)
|
|
phase_diff = np.abs(np.angle(mean) - np.angle(sample))
|
|
|
|
app.visualise_data(magn_diff, "magn-diff")
|
|
app.visualise_data(phase_diff, "phase-diff")
|
|
|
|
if (magn_diff > MAGN_THRESHOLD).any() or (phase_diff > PHASE_THRESHOLD).any():
|
|
print("Motion detected!")
|
|
sensor.update(True)
|
|
else:
|
|
print("No motion detected!")
|
|
sensor.update(False)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Start the application
|
|
print("Starting app")
|
|
app.set_producer(RealtimeCSIProducer())
|
|
app.start()
|