Bt monitor
This commit is contained in:
parent
e9ec94a5f8
commit
9092f08481
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"python.linting.pylintEnabled": false,
|
"python.linting.pylintEnabled": false,
|
||||||
"python.linting.enabled": true,
|
"python.linting.enabled": true,
|
||||||
"python.linting.flake8Enabled": true
|
"python.linting.flake8Enabled": true,
|
||||||
|
"files.associations": {
|
||||||
|
"*.yaml": "home-assistant"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -12,12 +12,16 @@ all:
|
||||||
sensor_room_name_ascii: prusaprinter
|
sensor_room_name_ascii: prusaprinter
|
||||||
sensor_room_name: prusaprinter
|
sensor_room_name: prusaprinter
|
||||||
dht_pin: 26
|
dht_pin: 26
|
||||||
|
main_user: root
|
||||||
bedroompi:
|
bedroompi:
|
||||||
squeezelite_name: BedroomPi
|
squeezelite_name: BedroomPi
|
||||||
shairport_name: BedroomPi
|
shairport_name: BedroomPi
|
||||||
alsa_card_name: Codec
|
alsa_card_name: Codec
|
||||||
sensor_room_name_ascii: schlafzimmer
|
sensor_room_name_ascii: schlafzimmer
|
||||||
sensor_room_name: Schlafzimmer
|
sensor_room_name: Schlafzimmer
|
||||||
|
my_bt_monitor_watchdog_seconds: 600
|
||||||
|
my_btmonitor_restart_ble_interface: hci0
|
||||||
|
main_user: root
|
||||||
kitchenpi:
|
kitchenpi:
|
||||||
squeezelite_name: KitchenPi
|
squeezelite_name: KitchenPi
|
||||||
shairport_name: KitchenPi
|
shairport_name: KitchenPi
|
||||||
|
@ -25,6 +29,7 @@ all:
|
||||||
sensor_room_name_ascii: kueche
|
sensor_room_name_ascii: kueche
|
||||||
sensor_room_name: Küche
|
sensor_room_name: Küche
|
||||||
hifiberry_overlay: hifiberry-amp
|
hifiberry_overlay: hifiberry-amp
|
||||||
|
main_user: root
|
||||||
esszimmerradio: # oben, eltern
|
esszimmerradio: # oben, eltern
|
||||||
squeezelite_name: Esszimmer
|
squeezelite_name: Esszimmer
|
||||||
shairport_name: _Oben_Esszimmer
|
shairport_name: _Oben_Esszimmer
|
||||||
|
@ -32,6 +37,7 @@ all:
|
||||||
squeezeserver: 192.168.178.100
|
squeezeserver: 192.168.178.100
|
||||||
configure_wifi: true
|
configure_wifi: true
|
||||||
alsa_card_name: 1
|
alsa_card_name: 1
|
||||||
|
main_user: root
|
||||||
musikserverwohnzimmeroben: # oben, eltern
|
musikserverwohnzimmeroben: # oben, eltern
|
||||||
squeezelite_name: Wohnzimmer
|
squeezelite_name: Wohnzimmer
|
||||||
shairport_name: _Oben_Wohnzimmer
|
shairport_name: _Oben_Wohnzimmer
|
||||||
|
@ -40,6 +46,7 @@ all:
|
||||||
sensor_room_name_ascii: wohnzimmeroben
|
sensor_room_name_ascii: wohnzimmeroben
|
||||||
sensor_room_name: WohnzimmerOben
|
sensor_room_name: WohnzimmerOben
|
||||||
hifiberry_overlay: hifiberry-dacplus
|
hifiberry_overlay: hifiberry-dacplus
|
||||||
|
main_user: root
|
||||||
musicmouse:
|
musicmouse:
|
||||||
squeezelite_name: MusicMouse
|
squeezelite_name: MusicMouse
|
||||||
shairport_name: MusicMouse
|
shairport_name: MusicMouse
|
||||||
|
@ -47,6 +54,7 @@ all:
|
||||||
hifiberry_overlay: hifiberry-dacplus
|
hifiberry_overlay: hifiberry-dacplus
|
||||||
sensor_room_name: Kinderzimmer
|
sensor_room_name: Kinderzimmer
|
||||||
sensor_room_name_ascii: kinderzimmer
|
sensor_room_name_ascii: kinderzimmer
|
||||||
|
main_user: root
|
||||||
newrpi:
|
newrpi:
|
||||||
squeezelite_name: MyTestRaspberry
|
squeezelite_name: MyTestRaspberry
|
||||||
shairport_name: MyTestRaspberry
|
shairport_name: MyTestRaspberry
|
||||||
|
@ -57,6 +65,9 @@ all:
|
||||||
server:
|
server:
|
||||||
sensor_room_name: Arbeitszimmer
|
sensor_room_name: Arbeitszimmer
|
||||||
sensor_room_name_ascii: arbeitszimmer
|
sensor_room_name_ascii: arbeitszimmer
|
||||||
|
my_bt_monitor_watchdog_seconds: 600
|
||||||
|
my_btmonitor_restart_ble_interface: hci0
|
||||||
|
main_user: core
|
||||||
homeassistant:
|
homeassistant:
|
||||||
sensor_room_name: Anschlussraum
|
sensor_room_name: Anschlussraum
|
||||||
sensor_room_name_ascii: anschlussraum
|
sensor_room_name_ascii: anschlussraum
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
function dot -w git -d "Manages dotfiles"
|
||||||
|
git --git-dir=$HOME/.dot --work-tree=$HOME $argv
|
||||||
|
end
|
|
@ -0,0 +1,47 @@
|
||||||
|
---
|
||||||
|
#
|
||||||
|
- name: Install packages
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- bat
|
||||||
|
- broot
|
||||||
|
- duf
|
||||||
|
- fd-find
|
||||||
|
- fish
|
||||||
|
- fzf
|
||||||
|
- git
|
||||||
|
- glances
|
||||||
|
- lsd
|
||||||
|
- neovim
|
||||||
|
- ripgrep
|
||||||
|
- tmux
|
||||||
|
- zoxide
|
||||||
|
|
||||||
|
# TODO: Dust, btm
|
||||||
|
|
||||||
|
block:
|
||||||
|
become: true
|
||||||
|
become_user: {{main_user}}
|
||||||
|
- name: Create bin folder
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: ~/bin
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
# Oh-my-fish
|
||||||
|
- name: get oh-my-fish repo
|
||||||
|
git:
|
||||||
|
repo: 'https://github.com/oh-my-fish/oh-my-fish.git'
|
||||||
|
dest: ~/.local/share/git/oh-my-fish
|
||||||
|
- name: install oh-my-fish
|
||||||
|
shell:
|
||||||
|
cmd: "bin/install --offline --noninteractive"
|
||||||
|
executable: /usr/bin/fish
|
||||||
|
chdir: ~/.local/share/git/oh-my-fish
|
||||||
|
creates:
|
||||||
|
- ~/.local/share/omf
|
||||||
|
- ~/.config/omf
|
||||||
|
- copy:
|
||||||
|
content: "bobthefish"
|
||||||
|
dst: "~/.config/omf/theme"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,13 +4,12 @@ from bleak import BleakScanner
|
||||||
from bleak.assigned_numbers import AdvertisementDataType
|
from bleak.assigned_numbers import AdvertisementDataType
|
||||||
from bleak.backends.bluezdbus.advertisement_monitor import OrPattern
|
from bleak.backends.bluezdbus.advertisement_monitor import OrPattern
|
||||||
from bleak.backends.bluezdbus.scanner import BlueZScannerArgs
|
from bleak.backends.bluezdbus.scanner import BlueZScannerArgs
|
||||||
from Cryptodome.Cipher import AES
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import asyncio_mqtt
|
import asyncio_mqtt
|
||||||
import json
|
import json
|
||||||
from typing import Dict
|
from datetime import datetime
|
||||||
import collections
|
import os
|
||||||
import numpy as np
|
import time
|
||||||
|
|
||||||
# ------------------- Config ----------------------------------------------------------------
|
# ------------------- Config ----------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -21,95 +20,21 @@ config = {
|
||||||
"password": "{{my_btmonitor_mqtt_password}}",
|
"password": "{{my_btmonitor_mqtt_password}}",
|
||||||
"room": "{{sensor_room_name_ascii}}"
|
"room": "{{sensor_room_name_ascii}}"
|
||||||
},
|
},
|
||||||
"irk_to_devicename": {
|
"watchdog_seconds": {{my_bt_monitor_watchdog_seconds | default(None)}},
|
||||||
"aa67542b82c0e05d65c27fb7e313aba5": "martins_apple_watch",
|
"restart_ble_interface": {{my_btmonitor_restart_ble_interface | default(None)}},
|
||||||
"840e3892644c1ebd1594a9069c14ce0d" : "martins_iphone",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --------------------------------------------------------------------------------------------
|
stop_event = asyncio.Event()
|
||||||
|
time_last_package_received = datetime.now()
|
||||||
def resolve_rpa(rpa: bytes, irk: bytes) -> bool:
|
|
||||||
"""Compares the random address rpa to an irk (secret key) and return True if it matches"""
|
|
||||||
assert len(rpa) == 6
|
|
||||||
assert len(irk) == 16
|
|
||||||
|
|
||||||
key = irk
|
|
||||||
plain_text = b'\x00' * 16
|
|
||||||
plain_text = bytearray(plain_text)
|
|
||||||
plain_text[15] = rpa[3]
|
|
||||||
plain_text[14] = rpa[4]
|
|
||||||
plain_text[13] = rpa[5]
|
|
||||||
plain_text = bytes(plain_text)
|
|
||||||
|
|
||||||
cipher = AES.new(key, AES.MODE_ECB)
|
|
||||||
cipher_text = cipher.encrypt(plain_text)
|
|
||||||
return cipher_text[15] == rpa[0] and cipher_text[14] == rpa[1] and cipher_text[13] == rpa[2]
|
|
||||||
|
|
||||||
|
|
||||||
def addr_to_bytes(addr:str) -> bytes:
|
async def on_device_found_callback(mqtt_client, room, device, advertising_data):
|
||||||
"""Converts a bluetooth mac address string with semicolons to bytes"""
|
global time_last_package_received
|
||||||
str_without_colons = addr.replace(":", "")
|
time_last_package_received = datetime.now()
|
||||||
bytearr = bytearray.fromhex(str_without_colons)
|
|
||||||
bytearr.reverse()
|
|
||||||
return bytes(bytearr)
|
|
||||||
|
|
||||||
|
|
||||||
def decode_address(addr: str, irks: Dict[str, str]):
|
|
||||||
"""
|
|
||||||
addr is a bluetooth address as a string e.g. 4d:24:12:12:34:10
|
|
||||||
irks is dict with irk as a hex string, mapping to device name
|
|
||||||
"""
|
|
||||||
for irk, name in irks.items():
|
|
||||||
if resolve_rpa(addr_to_bytes(addr), bytes.fromhex(irk)):
|
|
||||||
return name
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def estimate_distance(rssi, tx_power, pl0=73):
|
|
||||||
"""
|
|
||||||
RSSI in dBm
|
|
||||||
txPower is a transmitter parameter that calculated according to its physic layer and antenna in dBm
|
|
||||||
Return value in meter
|
|
||||||
|
|
||||||
You should calculate "PL0" in calibration stage:
|
|
||||||
PL0 = txPower - RSSI; // When distance is distance0 (distance0 = 1m or more)
|
|
||||||
|
|
||||||
SO, RSSI will be calculated by below formula:
|
|
||||||
RSSI = txPower - PL0 - 10 * n * log(distance/distance0) - G(t)
|
|
||||||
G(t) ~= 0 //This parameter is the main challenge in achiving to more accuracy.
|
|
||||||
n = 2 (Path Loss Exponent, in the free space is 2)
|
|
||||||
distance0 = 1 (m)
|
|
||||||
distance = 10 ^ ((txPower - RSSI - PL0 ) / (10 * n))
|
|
||||||
|
|
||||||
Read more details:
|
|
||||||
https://en.wikipedia.org/wiki/Log-distance_path_loss_model
|
|
||||||
"""
|
|
||||||
n = 3.5
|
|
||||||
return 10**((tx_power - rssi - pl0) / (10 * n))
|
|
||||||
|
|
||||||
|
|
||||||
def smooth(y, box_pts):
|
|
||||||
box = np.ones(box_pts)/box_pts
|
|
||||||
y_smooth = np.convolve(y, box, mode='same')
|
|
||||||
return y_smooth
|
|
||||||
|
|
||||||
WINDOW_SIZE = 6
|
|
||||||
last_values = collections.deque(maxlen=WINDOW_SIZE)
|
|
||||||
def filter_distance(dist: float):
|
|
||||||
global last_values
|
|
||||||
last_values.append(dist)
|
|
||||||
return smooth(np.array(last_values), 12)[-1]
|
|
||||||
|
|
||||||
|
|
||||||
async def on_device_found_callback(irks, mqtt_client, room, device, advertising_data):
|
|
||||||
#decoded_device_id = decode_address(device.address, irks)
|
|
||||||
rssi = advertising_data.rssi
|
rssi = advertising_data.rssi
|
||||||
tx_power = advertising_data.tx_power
|
tx_power = advertising_data.tx_power
|
||||||
if tx_power is not None and rssi is not None:
|
if tx_power is not None and rssi is not None:
|
||||||
topic = f"my_btmonitor/raw_measurements/{room}"
|
topic = f"my_btmonitor/raw_measurements/{room}"
|
||||||
#distance = estimate_distance(rssi, tx_power, {{my_btmonitor_pl0 | default('73')}} )
|
|
||||||
#filtered_distance = filter_distance(distance)
|
|
||||||
data = {"address": device.address,
|
data = {"address": device.address,
|
||||||
"rssi": rssi,
|
"rssi": rssi,
|
||||||
"tx_power": tx_power}
|
"tx_power": tx_power}
|
||||||
|
@ -118,19 +43,28 @@ async def on_device_found_callback(irks, mqtt_client, room, device, advertising_
|
||||||
except Exception:
|
except Exception:
|
||||||
print("Probably mqtt isn't running - exit whole script and let systemd restart it")
|
print("Probably mqtt isn't running - exit whole script and let systemd restart it")
|
||||||
exit(1)
|
exit(1)
|
||||||
#print(data)
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def watchdog():
|
||||||
stop_event = asyncio.Event()
|
global time_last_package_received
|
||||||
|
timeout = config["watchdog_seconds"]
|
||||||
|
if not timeout or timeout <= 0:
|
||||||
|
return
|
||||||
|
while True:
|
||||||
|
restart = (datetime.now() - time_last_package_received).seconds > timeout
|
||||||
|
if restart:
|
||||||
|
stop_event.set()
|
||||||
|
await asyncio.sleep(60)
|
||||||
|
|
||||||
|
|
||||||
|
async def ble_scan():
|
||||||
mqtt_conf = config['mqtt']
|
mqtt_conf = config['mqtt']
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
async with asyncio_mqtt.Client(hostname=mqtt_conf["hostname"],
|
async with asyncio_mqtt.Client(hostname=mqtt_conf["hostname"],
|
||||||
username=mqtt_conf["username"],
|
username=mqtt_conf["username"],
|
||||||
password=mqtt_conf['password']) as mqtt_client:
|
password=mqtt_conf['password']) as mqtt_client:
|
||||||
cb = partial(on_device_found_callback, config['irk_to_devicename'], mqtt_client, mqtt_conf['room'])
|
cb = partial(on_device_found_callback, mqtt_client, mqtt_conf['room'])
|
||||||
active_scan = True
|
active_scan = True
|
||||||
if active_scan:
|
if active_scan:
|
||||||
async with BleakScanner(cb) as scanner:
|
async with BleakScanner(cb) as scanner:
|
||||||
|
@ -146,4 +80,18 @@ async def main():
|
||||||
print("Error", e)
|
print("Error", e)
|
||||||
print("Starting again")
|
print("Starting again")
|
||||||
|
|
||||||
asyncio.run(main())
|
|
||||||
|
async def main():
|
||||||
|
await asyncio.gather(ble_scan(), watchdog())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
restart_interface = config["restart_ble_interface"]
|
||||||
|
if restart_interface:
|
||||||
|
print(f"Restarting {restart_interface}")
|
||||||
|
os.system(f"hciconfig {restart_interface} down")
|
||||||
|
time.sleep(3)
|
||||||
|
os.system(f"hciconfig {restart_interface} up")
|
||||||
|
time.sleep(3)
|
||||||
|
print("Done")
|
||||||
|
asyncio.run(main())
|
|
@ -1,36 +0,0 @@
|
||||||
---
|
|
||||||
- name: Install packages
|
|
||||||
apt:
|
|
||||||
name:
|
|
||||||
- bat
|
|
||||||
- fish
|
|
||||||
- fzf
|
|
||||||
- fd-find
|
|
||||||
- ripgrep
|
|
||||||
- lsd
|
|
||||||
- zoxide
|
|
||||||
|
|
||||||
- name: Check if oh-my-fish is installed
|
|
||||||
stat:
|
|
||||||
path: '/etc/omf.installed'
|
|
||||||
register: omf
|
|
||||||
|
|
||||||
- name: Download omf installer
|
|
||||||
get_url:
|
|
||||||
url: https://raw.githubusercontent.com/oh-my-fish/oh-my-fish/master/bin/install
|
|
||||||
|
|
||||||
- name: Execute omf installer
|
|
||||||
shell: /usr/bin/fish /tmp/install --noninteractive
|
|
||||||
|
|
||||||
- name: Execute omf installer
|
|
||||||
shell: /usr/bin/fish -c omf install
|
|
||||||
|
|
||||||
- name: Remove the omf installer
|
|
||||||
file:
|
|
||||||
path: /tmp/install
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Mark oh-my-fish installed with /etc/omf.installed
|
|
||||||
file:
|
|
||||||
path: /etc/omf.installed
|
|
||||||
state: touch
|
|
10
todo.md
10
todo.md
|
@ -23,3 +23,13 @@ dpkg-buildpackage -b -uc
|
||||||
[General]
|
[General]
|
||||||
Discoverable=false
|
Discoverable=false
|
||||||
Alias=bla
|
Alias=bla
|
||||||
|
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
- fish
|
||||||
|
- apt install fish
|
||||||
|
- oh my fish
|
||||||
|
- fish config
|
||||||
|
- ripgrep (apt install ripgrep)
|
||||||
|
- fd (apt install fd-find)
|
||||||
|
- apt install neovim tmux fd-find ripgrep lsd broot fzf tmux glances
|
Loading…
Reference in New Issue