Bt monitor

This commit is contained in:
Martin Bauer 2024-07-28 08:45:01 +02:00
parent e9ec94a5f8
commit 9092f08481
8 changed files with 27968 additions and 127 deletions

View File

@ -1,5 +1,8 @@
{
"python.linting.pylintEnabled": false,
"python.linting.enabled": true,
"python.linting.flake8Enabled": true
"python.linting.flake8Enabled": true,
"files.associations": {
"*.yaml": "home-assistant"
}
}

View File

@ -12,12 +12,16 @@ all:
sensor_room_name_ascii: prusaprinter
sensor_room_name: prusaprinter
dht_pin: 26
main_user: root
bedroompi:
squeezelite_name: BedroomPi
shairport_name: BedroomPi
alsa_card_name: Codec
sensor_room_name_ascii: schlafzimmer
sensor_room_name: Schlafzimmer
my_bt_monitor_watchdog_seconds: 600
my_btmonitor_restart_ble_interface: hci0
main_user: root
kitchenpi:
squeezelite_name: KitchenPi
shairport_name: KitchenPi
@ -25,6 +29,7 @@ all:
sensor_room_name_ascii: kueche
sensor_room_name: Küche
hifiberry_overlay: hifiberry-amp
main_user: root
esszimmerradio: # oben, eltern
squeezelite_name: Esszimmer
shairport_name: _Oben_Esszimmer
@ -32,6 +37,7 @@ all:
squeezeserver: 192.168.178.100
configure_wifi: true
alsa_card_name: 1
main_user: root
musikserverwohnzimmeroben: # oben, eltern
squeezelite_name: Wohnzimmer
shairport_name: _Oben_Wohnzimmer
@ -40,6 +46,7 @@ all:
sensor_room_name_ascii: wohnzimmeroben
sensor_room_name: WohnzimmerOben
hifiberry_overlay: hifiberry-dacplus
main_user: root
musicmouse:
squeezelite_name: MusicMouse
shairport_name: MusicMouse
@ -47,6 +54,7 @@ all:
hifiberry_overlay: hifiberry-dacplus
sensor_room_name: Kinderzimmer
sensor_room_name_ascii: kinderzimmer
main_user: root
newrpi:
squeezelite_name: MyTestRaspberry
shairport_name: MyTestRaspberry
@ -57,6 +65,9 @@ all:
server:
sensor_room_name: Arbeitszimmer
sensor_room_name_ascii: arbeitszimmer
my_bt_monitor_watchdog_seconds: 600
my_btmonitor_restart_ble_interface: hci0
main_user: core
homeassistant:
sensor_room_name: Anschlussraum
sensor_room_name_ascii: anschlussraum

View File

@ -0,0 +1,3 @@
function dot -w git -d "Manages dotfiles"
git --git-dir=$HOME/.dot --work-tree=$HOME $argv
end

View File

@ -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

View File

@ -4,13 +4,12 @@ from bleak import BleakScanner
from bleak.assigned_numbers import AdvertisementDataType
from bleak.backends.bluezdbus.advertisement_monitor import OrPattern
from bleak.backends.bluezdbus.scanner import BlueZScannerArgs
from Cryptodome.Cipher import AES
from functools import partial
import asyncio_mqtt
import json
from typing import Dict
import collections
import numpy as np
from datetime import datetime
import os
import time
# ------------------- Config ----------------------------------------------------------------
@ -21,95 +20,21 @@ config = {
"password": "{{my_btmonitor_mqtt_password}}",
"room": "{{sensor_room_name_ascii}}"
},
"irk_to_devicename": {
"aa67542b82c0e05d65c27fb7e313aba5": "martins_apple_watch",
"840e3892644c1ebd1594a9069c14ce0d" : "martins_iphone",
}
"watchdog_seconds": {{my_bt_monitor_watchdog_seconds | default(None)}},
"restart_ble_interface": {{my_btmonitor_restart_ble_interface | default(None)}},
}
# --------------------------------------------------------------------------------------------
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]
stop_event = asyncio.Event()
time_last_package_received = datetime.now()
def addr_to_bytes(addr:str) -> bytes:
"""Converts a bluetooth mac address string with semicolons to bytes"""
str_without_colons = addr.replace(":", "")
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)
async def on_device_found_callback(mqtt_client, room, device, advertising_data):
global time_last_package_received
time_last_package_received = datetime.now()
rssi = advertising_data.rssi
tx_power = advertising_data.tx_power
if tx_power is not None and rssi is not None:
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,
"rssi": rssi,
"tx_power": tx_power}
@ -118,19 +43,28 @@ async def on_device_found_callback(irks, mqtt_client, room, device, advertising_
except Exception:
print("Probably mqtt isn't running - exit whole script and let systemd restart it")
exit(1)
#print(data)
async def main():
stop_event = asyncio.Event()
async def watchdog():
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']
while True:
try:
async with asyncio_mqtt.Client(hostname=mqtt_conf["hostname"],
username=mqtt_conf["username"],
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
if active_scan:
async with BleakScanner(cb) as scanner:
@ -146,4 +80,18 @@ async def main():
print("Error", e)
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())

View File

@ -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
View File

@ -23,3 +23,13 @@ dpkg-buildpackage -b -uc
[General]
Discoverable=false
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