Compare commits

..

16 Commits

Author SHA1 Message Date
7c70221723 IR stuff update 2025-01-06 18:01:18 +01:00
Martin Bauer
9092f08481 Bt monitor 2024-07-28 08:45:01 +02:00
Martin Bauer
e9ec94a5f8 Bluetooth Monitor WIP 2024-03-29 09:32:59 +01:00
Martin Bauer
fe744b2285 bt monitor 2024-03-08 13:02:55 +01:00
Martin Bauer
ffeee72652 music mouse setup 2024-03-08 13:02:43 +01:00
Martin Bauer
fb6f10891d Bluetooth monitor and more 2024-03-01 15:01:08 +01:00
Martin Bauer
7501ef18a4 updates raspis to new os based on bookworm 2024-02-19 08:10:58 +01:00
Martin Bauer
7776095180 New server setup based on ubuntu 2023-09-19 10:55:47 +02:00
Martin Bauer
578be1a1cf updates 2023-01-03 20:07:56 +01:00
Martin Bauer
2a251d2700 Server scripts 2021-09-11 10:18:47 +02:00
Martin Bauer
f1d104a224 Updates & fixes 2021-07-20 15:35:12 +02:00
Martin Bauer
6c421a4ae1 Octopi & dht fixes 2021-07-05 19:50:15 +02:00
Martin Bauer
23a2c1fb50 added ir server, squeeze server, ... 2020-05-16 18:35:44 +02:00
Martin Bauer
93034dd0ec Update dht22 sensing code + sispmctl fixes 2020-05-12 20:51:04 +02:00
Martin Bauer
d8c9a491d1 Working sound setup for raspis 2020-05-10 15:25:38 +02:00
Martin Bauer
caf6232dfb Added sysdweb 2020-05-02 11:21:52 +02:00
138 changed files with 59981 additions and 429 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
ve_*
/server/scripts/docker-images/tagspace/tagspaces
/server/scripts/docker-images/tagspace/*.zip
*.img
/music
__pycache__
/roles/pi-squeezeserver/backup
venv*

8
.vscode/settings.json vendored Normal file
View File

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

61
full.yml Normal file
View File

@@ -0,0 +1,61 @@
---
#- hosts: esszimmerradio
# roles:
# - pi-standard-setup
# - pi-squeezelite-custom
# - pi-shairport
# - pi-lirc
# - pi-sispmctl
#
- hosts: musikserverwohnzimmeroben
roles:
- pi-standard-setup
- pi-hifiberry-amp
- pi-squeezelite-custom
- pi-shairport
- pi-irserver
#- pi-dhtsensor
- pi-squeezeserver
- hosts: kitchenpi
roles:
- pi-standard-setup
- pi-hifiberry-amp
- pi-squeezelite-custom
- pi-shairport
- pi-lirc
- pi-dhtsensor
- pi-disable-onboard-bluetooth
- bluetooth-monitor
- hosts: bedroompi
roles:
- pi-standard-setup
- pi-squeezelite-custom
- pi-shairport
- pi-lirc
- pi-dhtsensor
- pi-disable-onboard-bluetooth
- bluetooth-monitor
- hosts: musicmouse
roles:
- pi-standard-setup
- pi-hifiberry-amp
- pi-musicmouse
- pi-squeezelite-custom
- pi-shairport
- pi-lirc
- bluetooth-monitor
#- hosts: octopi
# roles:
# - pi-dhtsensor
#- hosts: newrpi
# roles:
# - pi-standard-setup
# - pi-lirc

84
inventory.yml Normal file
View File

@@ -0,0 +1,84 @@
all:
hosts:
server:
ansible_host: home.bauer.tech
ansible_port: 22187
server2:
children:
iot:
hosts:
octopi:
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
alsa_card_name: 0
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
#alsa_card_name: Device
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
alsa_card_name: 0
squeezeserver: 192.168.178.100
sensor_room_name_ascii: wohnzimmeroben
sensor_room_name: WohnzimmerOben
hifiberry_overlay: hifiberry-dacplus
main_user: root
musicmouse:
squeezelite_name: MusicMouse
shairport_name: MusicMouse
alsa_card_name: 1
hifiberry_overlay: hifiberry-dacplus
sensor_room_name: Kinderzimmer
sensor_room_name_ascii: kinderzimmer
main_user: root
newrpi:
squeezelite_name: MyTestRaspberry
shairport_name: MyTestRaspberry
alsa_card_name: 0
sensor_room_name_ascii: testraum
sensor_room_name: Test Raum
heatingpi:
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
vars:
ansible_user: root
ansible_python_interpreter: /usr/bin/python3
squeezeserver: 192.168.178.80
router_ip: 192.168.178.1
home_assistant_url: https://ha.bauer.tech
home_assistant_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkM2QxYjAwYjkxZjY0MWVhYjA4YmZhMDYwYTg3YjRhNyIsImlhdCI6MTcwNDI3MDU5MSwiZXhwIjoyMDE5NjMwNTkxfQ.dzvejgEQd9hf-Yftzd7NkR5pv76GaLFczeOy-a2pa1o
configure_wifi: false
wifi_ssid: BauerWLAN
my_btmonitor_mqtt_username: my_btmonitor
my_btmonitor_mqtt_password: 8aBIAC14jaKKbla

Binary file not shown.

View File

@@ -2,7 +2,7 @@
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from keepasshttplib import keepasshttplib, encrypter
import requests
DOCUMENTATION = """
lookup: keepass
@@ -39,12 +39,12 @@ class LookupModule(LookupBase):
else:
entry_path, entry_attr = terms[0], terms[1]
if not self._test_connection():
raise AnsibleError('Keepass is closed!')
#if not self._test_connection():
# raise AnsibleError('Keepass is closed!')
try:
auth = self.k.get_credentials(entry_path)
except Exception as e:
raise AnsibleError('Keepass error obtaining entry {}: {}'.format(host_name, e))
raise AnsibleError('Keepass error obtaining entry {}: {}'.format(entry_path, e))
if auth:
if entry_attr not in ('username', 'user', 'pass', 'passwd', 'password'):
raise AnsibleError("Keepass wrong entry")

8
newrpi-provisioning.yml Normal file
View File

@@ -0,0 +1,8 @@
---
- hosts: musicmouse
roles:
- pi-standard-setup
- pi-hifiberry-amp
- pi-squeezelite
- pi-shairport
- pi-lirc

3
octopisetup.yml Normal file
View File

@@ -0,0 +1,3 @@
- hosts: octopi
roles:
- pi-dhtsensor

View File

@@ -1,96 +0,0 @@
# Run with
# ansible-playbook 01-download-and-prepare-raspi-image.yml
---
- hosts: 127.0.0.1
connection: local
gather_facts: false
vars:
target_folder: "/media/martin/data_linux/tmp/"
vars_prompt:
- name: "ansible_become_pass"
prompt: "Sudo password to mount raspi image"
- name: "new_hostname"
prompt: "New hostname for the PI"
private: no
tasks:
# --- Preparation ---
- name: Download Raspian image
get_url:
url: "https://downloads.raspberrypi.org/raspbian_lite_latest"
dest: "{{target_folder}}/raspian_lite_latest.zip"
- name: Unpack Image
unarchive:
src: "{{target_folder}}/raspian_lite_latest.zip"
dest: "{{target_folder}}"
creates: "{{target_folder}}/*raspbian*.img"
- name: Make Folders to mount to
file:
path: "{{item}}"
state: directory
with_items:
- "{{target_folder}}/mounted_raspi_image"
- "{{target_folder}}/mounted_raspi_image/boot"
- "{{target_folder}}/mounted_raspi_image/system"
- name: Setup Loopback
become: true
shell:
cmd: "losetup -P /dev/loop42 {{target_folder}}/*raspbian*.img"
creates: "/dev/loop42p1"
- name: Mount Boot Partition
become: true
shell:
warn: false
cmd: "mount /dev/loop42p1 {{target_folder}}/mounted_raspi_image/boot"
creates: "{{target_folder}}/mounted_raspi_image/boot/kernel.img"
- name: Mount System Partition
become: true
shell:
warn: false
cmd: "mount /dev/loop42p2 {{target_folder}}/mounted_raspi_image/system"
creates: "{{target_folder}}/mounted_raspi_image/system/bin"
# --- Actual work ---
- name: "Add SSH File to boot partition to allow for first remote login"
become: true
file:
path: "{{target_folder}}/mounted_raspi_image/boot/ssh"
state: touch
- name: "Writing new hostname to /etc/hostname"
become: true
copy:
content: "{{new_hostname}}"
dest: "{{target_folder}}/mounted_raspi_image/system/etc/hostname"
# --- Wind-down
- name: Unmount System Partition
become: true
shell:
warn: false
cmd: "umount /dev/loop42p2"
removes: "{{target_folder}}/mounted_raspi_image/system/bin"
- name: Unmount Boot Partition
become: true
shell:
warn: false
cmd: "umount /dev/loop42p1"
removes: "{{target_folder}}/mounted_raspi_image/boot/kernel.img"
- name: Tear down loop device
become: true
shell:
cmd: "losetup -d /dev/loop42"
removes: "/dev/loop42p1"
- name: Remove folders
file:
path: "{{item}}"
state: absent
with_items:
- "{{target_folder}}/mounted_raspi_image"
- "{{target_folder}}/mounted_raspi_image/boot"
- "{{target_folder}}/mounted_raspi_image/system"
- "{{target_folder}}/raspian_lite_latest.zip"
- name: Final Image
debug:
msg: |
The prepared image is ready at {{target_folder}}.
Copy it to sdcard with
dd bs=4M status=progress if=the_image of=/dev/your/sdcard
use e.g. /dev/sdb not /dev/sdb1 !

View File

@@ -1,109 +0,0 @@
# Run with
# ansible-playbook -i raspberrypi, 02-provision_new_pi.yml
# where "raspberrypi" is the hostname of the pi
---
- hosts: kitchenpi
gather_facts: false
vars:
timezone: "Europe/Berlin"
wifi_country: "DE"
wifi_ssid: "" # put SSID here to configure wifi
wifi_pass_url: "bauer_wifi" # has to be in keepass with url "wifi_pass_url"
ansible_ssh_pass: raspberry
ansible_become: yes
ansible_become_password: raspberry
new_hostname: "" # set this to change the hostname
vars_prompt:
- name: ansible_user
prompt: "User to connect with, put in 'pi' here if you connect the first time, else leave empty"
default: root
tasks:
- name: Do apt update/upgrade
apt: upgrade=yes update_cache=yes cache_valid_time=7200
- name: Detect Raspi Model
slurp: src=/sys/firmware/devicetree/base/model
register: raspberry_model
- name: Show Raspi Model
debug: msg={{ raspberry_model.content | b64decode }}
- name: Add authorized SSH key to root account
authorized_key:
user: root
key: "{{ lookup('file', '../public_keys/martin_laptop.pub') }}"
state: present
- name: Activate root login with key
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PermitRootLogin"
line: "PermitRootLogin prohibit-password"
notify: restart sshd
- name: Deactive SSH accepting locale vars (leads to warnings)
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?AcceptEnv LANG LC_*"
line: "#AcceptEnv LANG LC_*"
notify: restart sshd
- name: Get hostname
command: "raspi-config nonint get_hostname"
register: pi_hostname
changed_when: False
- name: Change hostname to {{ new_hostname }}
command: "raspi-config nonint do_hostname {{ new_hostname }}"
when: new_hostname | bool and pi_hostname.stdout != new_hostname
- name: set boot mode to CLI
command: "raspi-config nonint do_boot_behaviour B1"
#I2 Change Timezone
- name: Change timezone
command: "raspi-config nonint do_change_timezone {{ timezone }}"
- name: Change locale
command: "raspi-config nonint do_change_locale en_US.UTF-8"
- name: Change password of default pi account
user:
name: pi
update_password: always
password: "{{ lookup('keepass', 'default_rpi_password') | password_hash('sha512') }}"
- name: Install Packages (vim, git, basic python stuff)
apt:
name:
- vim
- git
- python3
- python3-pip
- python3-wheel
cache_valid_time: 7200
state: present
- name: Copy vim config
copy: src=../configs/vimrc dest=/root/.vimrc
- name: Copy git config
copy: src=../configs/gitconfig dest=/root/.gitconfig
# Wifi
- name: Get WiFi country
command: "raspi-config nonint get_wifi_country"
register: wifi_country
changed_when: False
ignore_errors: yes #to avoid error when WiFi is not present
- name: Change WiFi country
command: "raspi-config nonint do_wifi_country {{ wifi_country }}"
- name: Set WiFi credentials
command: "raspi-config nonint do_wifi_ssid_passphrase {{ wifi_ssid }} {{ lookup('keepass', wifi_pass_url) }}"
when: wifi_ssid | bool
# Message of the day
- name: Set Message of the day
copy: src=configs/motd/{{ inventory_hostname }} dest=/etc/motd
#- name: Remove motd tail
# copy: dest=/etc/motd.
# LED off script
- name: Copy led off script
copy: src=configs/raspi-leds-off.sh dest=/usr/sbin/raspi-leds-off.sh mode="u+rwx"
- name: Copy led off service
copy: src=configs/raspi-leds-off.service dest=/lib/systemd/system/
- name: Activate led off servic
systemd: name=raspi-leds-off state=restarted enabled=yes daemon_reload=yes
handlers:
- name: restart sshd
service:
name: sshd
state: restarted

View File

@@ -1,44 +0,0 @@
# Install instructions taken from
# https://github.com/mikebrady/shairport-sync/blob/master/INSTALL.md
---
- hosts: kitchenpi
gather_facts: false
vars:
shairport_sync_version: "3.3.5"
remote_user: root
tasks:
- name: Apt install dependencies
apt:
cache_valid_time: 7200
state: present
name:
- build-essential
- git
- xmltoman
- autoconf
- automake
- libtool
- libpopt-dev
- libconfig-dev
- libasound2-dev
- avahi-daemon
- libavahi-client-dev
- libssl-dev
- libsoxr-dev
- name: Build and Install Shairport sync (may take a while)
script: "scripts/build-shairport-sync.sh ${shairport_sync_version}"
args:
creates: /usr/local/bin/shairport-sync
- name: Copy config
template: src=configs/shairport-sync.conf dest=/etc/shairport-sync.conf
- name: Sync alsa config
template: src=configs/asound.conf dest=/etc/asound.conf
- name: Modify service file to run as root
lineinfile:
path: /lib/systemd/system/shairport-sync.service
regexp: "^#?User="
line: "User=root"
- name: Restart shairport-sync
systemd: name=shairport-sync state=restarted enabled=yes daemon_reload=yes

View File

@@ -1,14 +0,0 @@
---
- hosts: kitchenpi
gather_facts: false
remote_user: root
tasks:
- name: Apt install squeezelite package
apt: name=squeezelite cache_valid_time=7200 state=present
- name: Install config file
template: src=configs/squeezelite.cfg dest=/etc/default/squeezelite
- name: Sync alsa config
template: src=configs/asound.conf dest=/etc/asound.conf
- name: Restart squeezelite
systemd: name=squeezelite state=restarted enabled=yes daemon_reload=yes

View File

@@ -1,49 +0,0 @@
---
# lirc needs to be custom compiled on this kernel
# https://gist.github.com/billpatrianakos/cb72e984d4730043fe79cbe5fc8f7941
- hosts: kitchenpi
gather_facts: false
remote_user: root
tasks:
#- name: Apt install lirc package
# apt: name=lirc cache_valid_time=7200 state=present
# ignore_errors: yes
- name: Install config file lirc_options.conf
copy: src=configs/lirc/lirc_options.conf dest=/etc/lirc/lirc_options.conf
- name: Install config file lircd.conf
copy: src=configs/lirc/lircd.conf dest=/etc/lirc/lircd.conf
- name: Install remote file
copy: src=configs/lirc/hauppauge.conf dest=/etc/lirc/hauppauge.conf
- name: create temporary directory
tempfile:
state: directory
suffix: temp
register: tempdir
- name: Copy over lirc customly compiled lirc packages
copy:
src: configs/lirc/debs/
dest: "{{ tempdir.path }}"
when: tempdir.path is defined
- name: Install custom lirc package 1
apt:
deb: "{{ tempdir.path }}/liblirc0_0.10.1-5.2_armhf.deb"
when: tempdir.path is defined
- name: Install custom lirc package 2
apt:
deb: "{{ tempdir.path }}/liblircclient0_0.10.1-5.2_armhf.deb"
when: tempdir.path is defined
- name: Install custom lirc package 3
apt:
deb: "{{ tempdir.path }}/lirc_0.10.1-5.2_armhf.deb"
when: tempdir.path is defined
- name: Activate overlay in boot config
lineinfile:
path: /boot/config.txt
regexp: "^#?dtoverlay=gpio-ir"
line: "dtoverlay=gpio-ir,gpio_pin=17"
register: boot_overlay
- name: Restart lircd
systemd: name=lircd state=restarted enabled=yes daemon_reload=yes
- name: Reboot if boot overlay changed
reboot:
when: boot_overlay.changed

View File

@@ -1,27 +0,0 @@
---
- hosts: newrpi
gather_facts: false
remote_user: root
tasks:
- name: Deactivate normal audio
lineinfile:
path: /boot/config.txt
regexp: "^#?dtparam=audio=on"
line: "#dtparam=audio=on"
register: boot_overlay1
- name: Activate Hifiberry
lineinfile:
path: /boot/config.txt
regexp: "^#?dtoverlay=hifiberry-amp"
line: "dtoverlay=hifiberry-amp"
register: boot_overlay2
#- name: Reboot if boot overlay changed
# reboot:
# when: boot_overlay1.changed or boot_overlay2.changed
## State in /boot/config.txt
# dtoverlay=hifiberry-amp
# # remove old:
# #dtparam=audio=on

View File

@@ -1,20 +0,0 @@
---
- hosts: kitchenpi
gather_facts: false
remote_user: root
tasks:
- name: apt install libgpiod2
apt: name=libgpiod2 cache_valid_time=7200 state=present
- name: pip install adafruit-circuitpython-dht
pip:
name: adafruit-circuitpython-dht
executable: pip3
- name: Install script config
template: src=configs/dht22_sensing.json dest=/etc/dht22_sensing.json
- name: Install script
copy: src=configs/dht22_sensing.py dest=/usr/bin/dht22_sensing owner=root mode=u+rwx
- name: Install systemd service file
copy: src=configs/dht22_sensing.service dest=/lib/systemd/system/
- name: Add script to autostart and start now
systemd: name=dht22_sensing state=restarted enabled=yes daemon_reload=yes

View File

@@ -1 +0,0 @@
include "hauppauge.conf"

View File

@@ -1,26 +0,0 @@
all:
hosts:
server:
ansible_host: home.bauer.tech
ansible_port: 22187
children:
iot:
hosts:
newrpi:
squeezelite_name: MyTestRaspberry
shairport_name: MyTestRaspberry
alsa_card_name: 0
sensor_room_name_ascii: testraum
sensor_room_name: Test Raum
heatingpi:
bedroompi:
kitchenpi:
squeezelite_name: KitchenPi
shairport_name: KitchenPi
alsa_card_name: 0
sensor_room_name_ascii: kueche
sensor_room_name: Küche
vars:
ansible_python_interpreter: /usr/bin/python3

View File

@@ -1,2 +0,0 @@
/etc/modules
-> bedroompi: snd-bcm2835

View File

@@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
import requests
key = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxNjkxMWIzZmQ4ZWU0NDI0OTg0MjA0ZDllMDhkNGRlMCIsImlhdCI6MTU3ODE3MDU5MSwiZXhwIjoxODkzNTMwNTkxfQ.i7CdXEZy9DV9KPHAl-msK0rOfIUlPYo4zwwJ4UGhXuc"
url = "https://ha.bauer.tech"
headers = {
'x-ha-access': key,
'Authorization': "Bearer {}".format(key)
}
apiurl = url + "/api/states/sensor.schlafzimmer_temperatur"
data = {
"state": "19",
"attributes": {
"device_class": "temperature",
"friendly_name": "Schlafzimmer Temperatur",
"unit_of_measurement": "°C"
}
}
r = requests.post(apiurl, json=data, headers=headers)
print(r)

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"

View File

@@ -0,0 +1,131 @@
#include <cmath>
#include <vector>
#include <iostream>
using real_t = double;
static constexpr real_t SPIKE_THRESHOLD = 1.0f; // Threshold for spike detection
static constexpr int NUM_READINGS = 12; // Number of readings to keep track of
class FilteredDistance {
public:
FilteredDistance(real_t minCutoff = 1e-1f, real_t beta = 1e-3, real_t dcutoff = 5e-3);
void addMeasurement(real_t dist, real_t time_now_in_seconds);
const real_t getMedianDistance() const;
const real_t getDistance() const;
const real_t getVariance() const;
bool hasValue() const { return lastTime != 0; }
private:
real_t minCutoff;
real_t beta;
real_t dcutoff;
real_t x, dx;
real_t lastDist;
real_t lastTime;
real_t getAlpha(real_t cutoff, real_t dT);
real_t readings[NUM_READINGS]; // Array to store readings
int readIndex; // Current position in the array
real_t total; // Total of the readings
real_t totalSquared; // Total of the squared readings
void initSpike(real_t dist);
real_t removeSpike(real_t dist);
};
FilteredDistance::FilteredDistance(real_t minCutoff, real_t beta, real_t dcutoff)
: minCutoff(minCutoff), beta(beta), dcutoff(dcutoff), x(0), dx(0), lastDist(0), lastTime(-1), total(0), totalSquared(0), readIndex(0) {
}
void FilteredDistance::initSpike(real_t dist) {
for (size_t i = 0; i < NUM_READINGS; i++) {
readings[i] = dist;
}
total = dist * NUM_READINGS;
totalSquared = dist * dist * NUM_READINGS; // Initialize sum of squared distances
}
real_t FilteredDistance::removeSpike(real_t dist) {
total -= readings[readIndex]; // Subtract the last reading
totalSquared -= readings[readIndex] * readings[readIndex]; // Subtract the square of the last reading
readings[readIndex] = dist; // Read the sensor
total += readings[readIndex]; // Add the reading to the total
totalSquared += readings[readIndex] * readings[readIndex]; // Add the square of the reading
readIndex = (readIndex + 1) % NUM_READINGS; // Advance to the next position in the array
auto average = total / static_cast<real_t>(NUM_READINGS); // Calculate the average
if (std::fabs(dist - average) > SPIKE_THRESHOLD)
return average; // Spike detected, use the average as the filtered value
return dist; // No spike, return the new value
}
void FilteredDistance::addMeasurement(real_t dist, real_t time_now_in_seconds) {
const bool initialized = lastTime >= 0;
const real_t elapsed = time_now_in_seconds - lastTime;
lastTime = time_now_in_seconds;
if (!initialized) {
x = dist; // Set initial filter state to the first reading
dx = 0; // Initial derivative is unknown, so we set it to zero
lastDist = dist;
initSpike(dist);
} else {
real_t dT = std::max(elapsed, real_t(0.05)); // Convert microseconds to seconds, enforce a minimum dT
const real_t alpha = getAlpha(minCutoff, dT);
const real_t dAlpha = getAlpha(dcutoff, dT);
dist = removeSpike(dist);
x += alpha * (dist - x);
dx = dAlpha * ((dist - lastDist) / dT);
lastDist = x + beta * dx;
std::cout << "alpha=" << alpha <<
" dAlpha=" << dAlpha <<
" dist=" << dist <<
" x=" << x <<
" dx=" << dx <<
" lastDist=" << lastDist <<
std::endl;
}
}
const real_t FilteredDistance::getDistance() const {
return lastDist;
}
real_t FilteredDistance::getAlpha(real_t cutoff, real_t dT) {
real_t tau = 1.0f / (2 * M_PI * cutoff);
return 1.0f / (1.0f + tau / dT);
}
const real_t FilteredDistance::getVariance() const {
auto mean = total / static_cast<real_t>(NUM_READINGS);
auto meanOfSquares = totalSquared / static_cast<real_t>(NUM_READINGS);
auto variance = meanOfSquares - (mean * mean); // Variance formula: E(X^2) - (E(X))^2
if (variance < 0.0f) return 0.0f;
return variance;
}
int main(int argc, char**argv)
{
FilteredDistance f;
std::vector<real_t> values = {1.5, 2.9, 5.3, 15.1, 1.5, 2.5, 1.5, 2.9, 5.3, 15.1};
real_t time = 0.0;
//std::cout << " result_cpp = [";
for(int i=0; i < 1; ++i)
for(auto value : values) {
f.addMeasurement(value, time);
time += 1.0;
//std::cout << f.getDistance() << ", ";
}
//std::cout << "]" << std::endl;
return 0;
}

View File

@@ -0,0 +1,91 @@
#from time import time
import math
from scipy import signal
# Taken from ESPresense C++ code
class FilteredDistance:
NUM_READINGS = 100
SPIKE_THRESHOLD = 1.0
def __init__(self, min_cutoff : float = 1e-1, beta : float = 1e-3, dcutoff : float = 5e-3):
self.min_cutoff = min_cutoff
self.beta = beta
self.dcutoff = dcutoff
self.x = 0
self.dx = 0
self.last_dist = 0
self.last_time = -1.0
self.total = 0
self.read_index = 0
self.readings = []
def _init_spike(self, dist : float):
self.readings = [dist] * self.NUM_READINGS
self.total = sum(self.readings)
def _remove_spike(self, dist: float):
self.total -= self.readings[self.read_index]
self.readings[self.read_index] = dist
self.total += dist
self.read_index = (self.read_index + 1) % self.NUM_READINGS
average = self.total / self.NUM_READINGS
if abs(dist - average) > self.SPIKE_THRESHOLD:
return average # spike detected
else:
return dist
def _get_alpha(self, cutoff : float, dT : float):
tau = 1 / (2 * math.pi * cutoff)
return 1 / (1 + tau / dT)
def add_measurement(self, dist : float, time_now_in_seconds: float):
initialized = (self.last_time >= 0.0)
elapsed = time_now_in_seconds - self.last_time
self.last_time = time_now_in_seconds
if not initialized:
self.x = dist
self.dx = 0
self.last_dist = dist
self._init_spike(dist)
else:
dT = max(elapsed, 0.05)
alpha = self._get_alpha(self.min_cutoff, dT)
d_alpha = self._get_alpha(self.dcutoff, dT)
dist = self._remove_spike(dist)
self.x += alpha * (dist - self.x)
self.dx = d_alpha * ((dist - self.last_dist) / dT)
self.last_dist = self.x + self.beta * self.dx
#print(f"{alpha=} {d_alpha=} {dist=} {self.x=} {self.dx=} {self.last_dist=}")
def get_distance(self):
return self.last_dist
def run_test(times, values, **kwargs):
f = FilteredDistance(**kwargs)
result = []
for t, value in zip(times, values):
f.add_measurement(value, t)
result.append(f.get_distance())
return result
def smooth(y, box_pts):
box = np.ones(box_pts)/box_pts
y_smooth = np.convolve(y, box, mode='same')
return y_smooth
if __name__ == "__main__":
import numpy as np
values = np.array([1] * 20 + [2, 4, 6, 7, 10, 16, 10, 13, 16, 24, 13] + [1] * 20 )
times = np.arange(0, len(values)) * 10
result_default = run_test(times, values)
result_beta1 = smooth(values, 6)
import matplotlib.pyplot as plt
plt.plot(times, values, label="raw")
#plt.plot(times, result_default, marker="o", label="filtered")
plt.plot(times, result_beta1, marker='x', label="altered")
plt.legend()
plt.show()

Binary file not shown.

View File

@@ -0,0 +1,11 @@
[Unit]
Description=My Bluetooth monitor
After=network.target
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/my_btmonitor
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,11 @@
FROM python:3
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY bt_monitor_server.py .
COPY training_data.csv .
CMD [ "python", "./bt_monitor_server.py" ]

View File

@@ -0,0 +1,95 @@
from pathlib import Path
import pandas as pd
import numpy as np
from copy import copy
from sklearn.model_selection import cross_val_score
from sklearn import svm
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
def load_measurements(csv_file: Path):
def cleanup_column_name(col_name: str):
clean_name = col_name.replace('#', '').strip()
if clean_name == 'room':
return 'tracker'
return clean_name
df = pd.read_csv(str(csv_file))
# String cleanup in column names and room names
df = df.rename(columns=cleanup_column_name)
df.applymap(lambda x: x.strip() if isinstance(x, str) else x)
df['tracker'] = df['tracker'].astype("category")
df['real_room'] = df['real_room'].astype("category")
return df
FAR_AWAY_FEATURE_VALUE = 1
def get_feature_value(rssi, tx_power):
MIN_RSSI = -90
MAX_TRANSFORMED_RSSI = 40
v = tx_power - rssi - MAX_TRANSFORMED_RSSI
if v < 0:
v = 0
return v / (-MIN_RSSI)
def make_training_data(df: pd.DataFrame, device_to_map):
idx_to_tracker = dict(enumerate(df['tracker'].cat.categories ))
tracker_to_idx = {v: k for k, v in idx_to_tracker.items()}
idx_to_room = dict(enumerate(df['real_room'].cat.categories ))
room_to_idx = {v: k for k, v in idx_to_room.items()}
last_real_room = None
start_time = None
current_feature = [FAR_AWAY_FEATURE_VALUE] * len(idx_to_tracker)
features = []
labels = []
# Feature vectors - rssi column for each room
for i, row in df.iterrows():
time, device, tracker, rssi, tx_power, real_room = row
if device != device_to_map:
continue
if last_real_room != real_room:
start_time = time
last_real_room = real_room
tracker_idx = tracker_to_idx[tracker]
current_feature[tracker_idx] = get_feature_value(rssi, tx_power)
if time - start_time > 20:
features.append(copy(current_feature))
labels.append(room_to_idx[real_room])
return np.array(features), np.array(labels)
def train(features, labels, classes):
clf = svm.SVC(kernel='rbf')
print("Training")
scores = cross_val_score(clf, features, labels, cv=5)
print(scores)
print("%0.2f accuracy with a standard deviation of %0.2f" % (scores.mean(), scores.std()))
X_train, X_test, y_train, y_test = train_test_split(features, labels, random_state=0)
clf.fit(X_train, y_train)
cm = confusion_matrix(clf.predict(X_test), y_test)
print(cm)
print(classes)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=classes)
disp.plot()
plt.show()
if __name__ == "__main__":
csv_path = Path("/home/martin/code/ansible/roles/bluetooth-monitor/other/collected.csv")
df = load_measurements(csv_path)
features, labels = make_training_data(df, "martins_apple_watch")
print(np.unique(labels))
print(features.shape, labels.shape)
train(features, labels, list(df['real_room'].dtype.categories))

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,309 @@
#!/usr/bin/env python3
import os
import aiomqtt
import json
import asyncio
from time import time
from pathlib import Path
from collections import namedtuple, defaultdict, deque
from typing import Dict, Optional, List
from Crypto.Cipher import AES
import pandas as pd
import numpy as np
import logging
from sklearn import svm
from sklearn.model_selection import cross_val_score
logging.basicConfig(level=logging.INFO)
BtleMeasurement = namedtuple("BtleMeasurement", ["time", "tracker", "address", "rssi", "tx_power"])
BtleDeviceMeasurement = namedtuple("BtleDeviceMeasurement", ["time", "device", "tracker", "rssi", "tx_power"])
MqttInfo = namedtuple("MqttInfo", ["server", "username", "password"])
# ------------------------------------------------------- DECODING -------------------------------------------------------------------------
class DeviceDecoder:
"""Decode bluetooth addresses - either simple ones (just address to name) or random changing ones like Apple devices using irk keys"""
def __init__(self, irk_to_devicename: Dict[str, str], address_to_name: Dict[str, str]):
"""
address_to_name: dictionary from bt address as string separated by ":" to a device name
irk_to_devicename is dict with irk as a hex string, mapping to device name
"""
self.irk_to_devicename = {bytes.fromhex(k): v for k, v in irk_to_devicename.items()}
self.address_to_name = address_to_name
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:
"""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(self, addr: str) -> Optional[str]:
"""addr is a bluetooth address as a string e.g. 4d:24:12:12:34:10"""
for irk, name in self.irk_to_devicename.items():
if DeviceDecoder._resolve_rpa(DeviceDecoder._addr_to_bytes(addr), irk):
return name
return self.address_to_name.get(addr, None)
def __call__(self, m: BtleMeasurement) -> Optional[BtleDeviceMeasurement]:
decoded_device_name = self.decode(m.address)
if not decoded_device_name:
return None
return BtleDeviceMeasurement(m.time, decoded_device_name, m.tracker, m.rssi, m.tx_power)
# ------------------------------------------------------- MACHINE LEARNING ----------------------------------------------------------------
class KnownRoomCsvLogger:
"""Logs known room measurements to be used later as training data for classifier"""
def __init__(self, csv_file: Path):
self.known_room = None
if csv_file.exists():
self.csv_file_handle = open(csv_file, "a")
else:
self.csv_file_handle = open(csv_file, "w")
print(f"#time,device,tracker,rssi,tx_power,known_room", file=csv_file)
def update_known_room(self, known_room: str):
if known_room != self.known_room:
logging.info(f"Updating known_room {self.known_room} -> {known_room}")
self.known_room = known_room
def report_measure(self, m: BtleDeviceMeasurement):
ignore_rooms = ("keins", "?", "none", "unknown")
if self.known_room is None or self.known_room in ignore_rooms:
return
logging.info(f"Appending to training set: {m}")
print(
f"{m.time},{m.device},{m.tracker},{m.rssi},{m.tx_power},{self.known_room}",
file=self.csv_file_handle,)
class RunningFeatureVector:
FAR_AWAY_FEATURE_VALUE = 1
MIN_TIME_UNTIL_PREDICTION = 40 # wait until every reachable tracker detected the device
TIME_TO_DELETE_IF_NOT_SEEN = 30 # if device wasn't spotted for this time period, the measure is set to inf
def __init__(self, trackers: List[str]):
self.trackers = trackers
self.feature_vecs_per_device = defaultdict(lambda: [self.FAR_AWAY_FEATURE_VALUE] * len(trackers))
self.last_measurements = deque()
self.tracker_name_to_idx = {name: i for i, name in enumerate(trackers)}
self.start_time = None
@staticmethod
def _get_feature_value(rssi, tx_power):
"""Transforms rssi and tx power into a value between 0 and 1, where 0 is close and 1 is far away"""
MIN_RSSI = -90
MAX_TRANSFORMED_RSSI = 40
v = tx_power - rssi - MAX_TRANSFORMED_RSSI
if v < 0:
v = 0
return v / (-MIN_RSSI)
def add_measurement(self, new_measurement: BtleDeviceMeasurement):
if self.start_time is None:
self.start_time = new_measurement.time
self.last_measurements.append(new_measurement)
while len(self.last_measurements) > 0 and new_measurement.time - self.last_measurements[0].time > self.TIME_TO_DELETE_IF_NOT_SEEN:
self.last_measurements.popleft()
feature_vec = [self.FAR_AWAY_FEATURE_VALUE] * len(self.trackers)
for m in self.last_measurements:
if m.device == new_measurement.device:
tracker_idx = self.tracker_name_to_idx[m.tracker]
feature_vec[tracker_idx] = self._get_feature_value(m.rssi, m.tx_power)
return feature_vec if new_measurement.time - self.start_time > self.MIN_TIME_UNTIL_PREDICTION else None
def training_data_from_df(df: pd.DataFrame, device_to_train: str):
"""Returns a feature matrix (num_measurement, num_trackers) and a label vector (both numeric) to be used in scikit learn"""
trackers = list(df["tracker"].cat.categories)
idx_to_room = dict(enumerate(df["known_room"].cat.categories))
room_to_idx = {v: k for k, v in idx_to_room.items()}
last_known_room = None
features = []
labels = []
feature_accumulator = RunningFeatureVector(trackers)
# Feature vectors - rssi column for each room
for i, row in df.iterrows():
time, device, tracker, rssi, tx_power, known_room = row
m = BtleDeviceMeasurement(time, device, tracker, rssi, tx_power)
if device != device_to_train:
continue
if last_known_room != known_room:
feature_accumulator = RunningFeatureVector(trackers) # reset for new room
last_known_room = known_room
feature_vec = feature_accumulator.add_measurement(m)
if feature_vec is not None:
features.append(feature_vec)
labels.append(room_to_idx[known_room])
return np.array(features), np.array(labels)
def load_measurements_from_csv(csv_file: Path) -> pd.DataFrame:
"""Load csv with training data into dataframe"""
def cleanup_column_name(col_name: str):
return col_name.replace("#", "").strip()
df = pd.read_csv(str(csv_file))
# String cleanup in column names and room names
df = df.rename(columns=cleanup_column_name)
df.map(lambda x: x.strip() if isinstance(x, str) else x)
df["tracker"] = df["tracker"].astype("category")
df["known_room"] = df["known_room"].astype("category")
df['device'] = df['device'].astype("category")
return df
async def send_discovery_messages(mqtt_client, device_names):
for device_name in device_names:
topic = f"homeassistant/sensor/my_btmonitor/{device_name}/config"
msg = {
"name": device_name,
"state_topic": f"my_btmonitor/ml/{device_name}",
"expire_after": 30,
"unique_id": device_name,
}
await mqtt_client.publish(topic, json.dumps(msg).encode(), retain=True)
async def async_main(
mqtt_info: MqttInfo,
trackers: List[str],
devices: List[str],
classifier,
device_decoder: DeviceDecoder,
training_data_logger: KnownRoomCsvLogger,
):
current_rooms = defaultdict(lambda: "unknown")
feature_accumulator = RunningFeatureVector(trackers)
async with aiomqtt.Client(
hostname=mqtt_info.server, username=mqtt_info.username, password=mqtt_info.password
) as client:
await send_discovery_messages(client, devices)
await client.subscribe("my_btmonitor/#")
async for message in client.messages:
current_time = time()
topic = message.topic
if topic.value == "my_btmonitor/known_room":
training_data_logger.update_known_room(message.payload.decode())
else:
splitted_topic = message.topic.value.split("/")
if splitted_topic[0] == "my_btmonitor" and splitted_topic[1] == "raw_measurements":
msg_json = json.loads(message.payload)
measurement = BtleMeasurement(
time=current_time,
tracker=splitted_topic[2],
address=msg_json["address"],
rssi=msg_json["rssi"],
tx_power=msg_json.get("tx_power", 0),
)
logging.debug(f"Got Measurement {measurement}")
m = device_decoder(measurement)
if m is not None:
logging.info(f"Decoded Measurement {m}")
training_data_logger.report_measure(m)
feature_vec =feature_accumulator.add_measurement(m)
if feature_vec:
feature_str={tracker : value for tracker, value in zip(trackers, feature_vec)}
logging.info(f"Features: {feature_str}")
if feature_vec is not None and classifier is not None:
room = classifier(m.device, feature_vec)
if room != current_rooms[m.device]:
logging.info(f"{m.device} moved room {current_rooms[m.device]} to {room}")
current_rooms[m.device] = room
await client.publish(f"my_btmonitor/ml/{m.device}", room.encode())
async def async_main_with_restart(
mqtt_info: MqttInfo,
trackers: List[str],
devices: List[str],
classifier,
device_decoder: DeviceDecoder,
training_data_logger: KnownRoomCsvLogger,
):
while True:
try:
await async_main(mqtt_info, trackers, devices, classifier, device_decoder, training_data_logger)
except Exception as e:
print(e)
print("restarting...")
def get_classification_func(training_df: pd.DataFrame, log_classifier_scores=True):
devices_to_track = list(training_df["device"].unique())
classifiers = {}
rooms = list(training_df["known_room"].dtype.categories)
for device_to_track in devices_to_track:
features, labels = training_data_from_df(training_df, device_to_track)
clf = svm.SVC(kernel="rbf")
logging.info(f"Computing cross validation score for {device_to_track}")
if log_classifier_scores:
scores = cross_val_score(clf, features, labels, cv=5)
logging.info(" %0.2f accuracy with a standard deviation of %0.2f" % (scores.mean(), scores.std()))
logging.info(f"Training SVM classifier for {device_to_track}")
clf.fit(features, labels)
classifiers[device_to_track] = clf
def classify(device_name, feature_vec):
room_idx = classifiers[device_name].predict([feature_vec])[0]
return rooms[room_idx]
return classify
if __name__ == "__main__":
mqtt_info = MqttInfo(server="homeassistant.fritz.box", username="my_btmonitor", password="8aBIAC14jaKKbla")
# Dict with bt addresses as strings to device name
address_to_name = {}
# Devices with random addresses - need irk key
irk_to_devicename = {
"aa67542b82c0e05d65c27fb7e313aba5": "martins_apple_watch",
"840e3892644c1ebd1594a9069c14ce0d": "martins_iphone",
}
script_path = os.path.dirname(os.path.realpath(__file__))
data_file = Path(script_path) / Path("training_data.csv")
training_df = load_measurements_from_csv(data_file)
classification_func = get_classification_func(training_df)
training_data_logger = KnownRoomCsvLogger(data_file)
device_decoder = DeviceDecoder(irk_to_devicename, address_to_name)
trackers = list(training_df["tracker"].cat.categories)
devices = list(training_df['device'].cat.categories)
asyncio.run(async_main_with_restart(mqtt_info, trackers, devices, classification_func, device_decoder, training_data_logger))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
aiomqtt==2.0.0
numpy==1.26.4
pandas==2.2.1
pycryptodome==3.20.0
scikit-learn==1.4.1.post1
scipy==1.12.0
typing_extensions==4.10.0

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
- name: Apt install bluez, firmware and Python requirements
apt:
name:
- bluez
- bluez-firmware
- firmware-realtek
- firmware-realtek-rtl8723cs-bt
- python3-pycryptodome
- python3-bleak
- python3-asyncio-mqtt
- python3-numpy
- name: Copy monitor script
template: src=my_btmonitor.py dest=/usr/bin/my_btmonitor owner=root mode=u+rwx
- name: Install systemd service file
copy: src=my_btmonitor.service dest=/etc/systemd/system/
- name: Add script to autostart and start now
systemd: name=my_btmonitor state=restarted enabled=yes daemon_reload=yes
#- name: Add to sysdweb
# include_role:
# name: pi-sysdweb
# vars:
# sysdweb_name: my_btmonitor

View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
import asyncio
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 functools import partial
import asyncio_mqtt
import json
from datetime import datetime
import os
import time
# ------------------- Config ----------------------------------------------------------------
config = {
"mqtt": {
"hostname": "homeassistant.fritz.box",
"username": "{{my_btmonitor_mqtt_username}}",
"password": "{{my_btmonitor_mqtt_password}}",
"room": "{{sensor_room_name_ascii}}"
},
"watchdog_seconds": {{my_bt_monitor_watchdog_seconds | default(None)}},
"restart_ble_interface": {{my_btmonitor_restart_ble_interface | default(None)}},
}
stop_event = asyncio.Event()
time_last_package_received = datetime.now()
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}"
data = {"address": device.address,
"rssi": rssi,
"tx_power": tx_power}
try:
await mqtt_client.publish(topic, json.dumps(data).encode())
except Exception:
print("Probably mqtt isn't running - exit whole script and let systemd restart it")
exit(1)
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, mqtt_client, mqtt_conf['room'])
active_scan = True
if active_scan:
async with BleakScanner(cb) as scanner:
await stop_event.wait()
else:
# Doesn't work, because of the strange or_patters
args = BlueZScannerArgs(
or_patterns=[OrPattern(0, AdvertisementDataType.MANUFACTURER_SPECIFIC_DATA, b"\x00\x4c")]
)
async with BleakScanner(cb, bluez=args, scanning_mode="passive") as scanner:
await stop_event.wait()
except Exception as e:
print("Error", e)
print("Starting again")
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

@@ -0,0 +1,3 @@
---
- name: Sync alsa config
template: src=asound.conf dest=/etc/asound.conf

View File

@@ -0,0 +1,3 @@
---
dht_pin: "D12"
dht_polling_sleep_time_seconds: 20

View File

@@ -4,20 +4,21 @@
import requests
import json
import time
import board
import adafruit_dht
import multiprocessing
config = json.load(open("/etc/dht22_sensing.json"))
dht_device = adafruit_dht.DHT22(getattr(board, config['dht_pin']))
def send_to_home_assistant(temperature, humidity):
# print(f"Sending temperature {temperature}, humidity {humidity}")
headers = {
'x-ha-access': config['token'],
'Authorization': "Bearer {}".format(config['token'])
}
temperature_url = "{}/api/states/sensor.{}".format(config['ha_url'], config['ha_temp_sensor_name'])
temperature_url = "{}/api/states/sensor.{}".format(
config['ha_url'], config['ha_temp_sensor_name'])
temperatur_data = {
"state": str(temperature),
"attributes": {
@@ -28,7 +29,8 @@ def send_to_home_assistant(temperature, humidity):
}
requests.post(temperature_url, json=temperatur_data, headers=headers)
humidity_url = "{}/api/states/sensor.{}".format(config['ha_url'], config['ha_humidity_sensor_name'])
humidity_url = "{}/api/states/sensor.{}".format(
config['ha_url'], config['ha_humidity_sensor_name'])
humidity_data = {
"state": str(humidity),
"attributes": {
@@ -41,13 +43,22 @@ def send_to_home_assistant(temperature, humidity):
def sense():
import adafruit_dht
import board
dht_device = adafruit_dht.DHT22(getattr(board, config['dht_pin']))
try:
send_to_home_assistant(dht_device.temperature, dht_device.humidity)
except RuntimeError as error:
# Errors happen fairly often, DHT's are hard to read, just keep going
print(error.args[0])
time.sleep(config['polling_sleep_time_seconds'])
if __name__ == "__main__":
multiprocessing.set_start_method('spawn')
while True:
sense()
process = multiprocessing.Process(target=sense)
process.start()
process.join()
time.sleep(float(config['polling_sleep_time_seconds']))

View File

@@ -0,0 +1,20 @@
---
- name: apt install libgpiod2
apt: name=libgpiod2 cache_valid_time=7200 state=present
- name: pip install adafruit-circuitpython-dht
pip:
name: adafruit-circuitpython-dht
executable: pip3
- name: Install script config
template: src=dht22_sensing.json dest=/etc/dht22_sensing.json
- name: Install script
copy: src=dht22_sensing.py dest=/usr/bin/dht22_sensing owner=root mode=u+rwx
- name: Install systemd service file
copy: src=dht22_sensing.service dest=/lib/systemd/system/
- name: Add script to autostart and start now
systemd: name=dht22_sensing state=started enabled=yes daemon_reload=yes
- name: Add to sysdweb
include_role:
name: pi-sysdweb
vars:
sysdweb_name: dht22_sensing

View File

@@ -1,8 +1,8 @@
{
"ha_url": "https://ha.bauer.tech",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiIxNjkxMWIzZmQ4ZWU0NDI0OTg0MjA0ZDllMDhkNGRlMCIsImlhdCI6MTU3ODE3MDU5MSwiZXhwIjoxODkzNTMwNTkxfQ.i7CdXEZy9DV9KPHAl-msK0rOfIUlPYo4zwwJ4UGhXuc",
"dht_pin": "D12",
"polling_sleep_time_seconds": 5,
"ha_url": "{{home_assistant_url}}",
"token": "{{home_assistant_token}}",
"dht_pin": "{{dht_pin}}",
"polling_sleep_time_seconds": "{{dht_polling_sleep_time_seconds}}",
"ha_temp_sensor_name": "{{sensor_room_name_ascii|lower}}_dht22_temperatur",
"ha_temp_friendly_name": "{{sensor_room_name}} Temperatur",

Binary file not shown.

View File

@@ -0,0 +1,3 @@
---
dht_pin: 12
dht_polling_sleep_time_seconds: 20

View File

@@ -0,0 +1,93 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import requests
import json
import time
import Adafruit_DHT
config = json.load(open("/etc/dht22_sensing.json"))
class Filter:
def __init__(self, filter_length, max_mean_deviation):
self._length = filter_length
self.values = []
self.max_mean_deviation = max_mean_deviation
def mean(self):
return sum(self.values) / len(self.values)
def is_valid_value(self, val):
if len(self.values) == 0:
return True
else:
return abs(val - self.mean()) < self.max_mean_deviation
def add_if_valid(self, val):
if val is None:
return False
if self.is_valid_value(val):
self.values.append(val)
if len(self.values) > self._length:
self.values.pop(0)
return True
else:
return False
def send_to_home_assistant(temperature, humidity):
# print(f"Sending temperature {temperature}, humidity {humidity}")
headers = {
'x-ha-access': config['token'],
'Authorization': "Bearer {}".format(config['token'])
}
temperature_url = "{}/api/states/sensor.{}".format(
config['ha_url'], config['ha_temp_sensor_name'])
temperatur_data = {
"state": "{:.1f}".format(temperature),
"attributes": {
"device_class": "temperature",
"friendly_name": config['ha_temp_friendly_name'],
"unit_of_measurement": "°C"
}
}
requests.post(temperature_url, json=temperatur_data, headers=headers)
humidity_url = "{}/api/states/sensor.{}".format(
config['ha_url'], config['ha_humidity_sensor_name'])
humidity_data = {
"state": "{:.1f}".format(humidity),
"attributes": {
"device_class": "humidity",
"friendly_name": config['ha_humidity_friendly_name'],
"unit_of_measurement": "%"
}
}
requests.post(humidity_url, json=humidity_data, headers=headers)
if __name__ == "__main__":
temp_filter = Filter(4, 10)
humidity_filter = Filter(4, 10)
while True:
sensor = Adafruit_DHT.DHT22
humidity, temperature = Adafruit_DHT.read_retry(
sensor, config['dht_pin'])
temp_valid = temp_filter.add_if_valid(temperature)
humidity_valid = humidity_filter.add_if_valid(humidity)
if not (temp_valid and humidity_valid):
print(
f"Discarding value {temperature}C / {humidity}%, because to far from means {temp_filter.mean()}C, {humidity_filter.mean()}%")
else:
try:
send_to_home_assistant(temperature, humidity)
except Exception as e:
print("Failed sending to home assistant")
print(e)
time.sleep(float(config['polling_sleep_time_seconds']))

View File

@@ -0,0 +1,10 @@
[Unit]
Description=DHT22 Temperature Humidity Sensing
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/dht22_sensing
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,31 @@
---
# Use the deprecated version here instead, not the new circuitpython adafruit-circuitpython-dht version
# the new version needs a lot of CPU time and doesn't work correctly on old raspi 1
# a copy of the deprecated repo is downloaded as zip (if it goes away)
- name: Uninstall libgpiod2 (circuitpython) if present
apt: name=libgpiod2 cache_valid_time=7200 state=absent
- name: pip uninstall adafruit-circuitpython-dht
pip:
name: adafruit-circuitpython-dht
executable: pip3
state: absent
extra_args: "--break-system-packages"
- name: pip install adafruit-dht
pip:
name: adafruit-dht
executable: pip3
extra_args: "--break-system-packages"
- name: Install script config
template: src=dht22_sensing.json dest=/etc/dht22_sensing.json
- name: Install script
copy: src=dht22_sensing.py dest=/usr/bin/dht22_sensing owner=root mode=u+rwx
- name: Install systemd service file
copy: src=dht22_sensing.service dest=/etc/systemd/system/
- name: Add script to autostart and start now
systemd: name=dht22_sensing state=restarted enabled=yes daemon_reload=yes
- name: Add to sysdweb
include_role:
name: pi-sysdweb
vars:
sysdweb_name: dht22_sensing

View File

@@ -0,0 +1,12 @@
{
"ha_url": "{{home_assistant_url}}",
"token": "{{home_assistant_token}}",
"dht_pin": "{{dht_pin}}",
"polling_sleep_time_seconds": "{{dht_polling_sleep_time_seconds}}",
"ha_temp_sensor_name": "{{sensor_room_name_ascii|lower}}_dht22_temperatur",
"ha_temp_friendly_name": "{{sensor_room_name}} Temperatur",
"ha_humidity_sensor_name": "{{sensor_room_name_ascii|lower}}_dht22_luftfeuchtigkeit",
"ha_humidity_friendly_name": "{{sensor_room_name}} Luftfeuchtigkeit"
}

View File

@@ -0,0 +1,4 @@
---
- name: reboot
reboot:

View File

@@ -0,0 +1,7 @@
---
- name: Deactivate onboard bluetooth
lineinfile:
path: /boot/firmware/config.txt
regexp: "^#?dtoverlay=disable-bt"
line: "dtoverlay=disable-bt"
notify: reboot

View File

@@ -0,0 +1,4 @@
---
- name: reboot
reboot:

View File

@@ -0,0 +1,3 @@
---
dependencies:
- role: pi-alsasetup

View File

@@ -0,0 +1,13 @@
---
- name: Deactivate normal audio
lineinfile:
path: /boot/config.txt
regexp: "^#?dtparam=audio=on"
line: "#dtparam=audio=on"
notify: reboot
- name: Activate Hifiberry
lineinfile:
path: /boot/firmware/config.txt
regexp: "^#?dtoverlay=hifiberry-amp"
line: "dtoverlay={{hifiberry_overlay}}"
notify: reboot

View File

@@ -0,0 +1,53 @@
[REMOTE]
[NAME]hauppauge
[TIMING]
[0][N]0[RC]2[RP]87[FREQ]36[RC5]
[COMMANDS]
[BTN_1][T]0[D]11011110000001
[BTN_2][T]0[D]11011110000010
[BTN_3][T]0[D]11011110000011
[BTN_4][T]0[D]11011110000100
[BTN_5][T]0[D]11011110000101
[BTN_6][T]0[D]11011110000110
[BTN_7][T]0[D]11011110000111
[BTN_8][T]0[D]11011110001000
[BTN_9][T]0[D]11011110001001
[BTN_0][T]0[D]11011110000000
[KEY_NUMERIC_STAR][T]0[D]11011110001010
[KEY_NUMERIC_POUND][T]0[D]11011110001110
[KEY_RED][T]0[D]11011110001011
[KEY_GREEN][T]0[D]11011110101110
[KEY_YELLOW][T]0[D]11011110111000
[KEY_BLUE][T]0[D]11011110101001
[KEY_PLAY][T]0[D]11011110110101
[KEY_PAUSE][T]0[D]11011110110000
[KEY_RECORD][T]0[D]11011110110111
[KEY_PREVIOUS][T]0[D]11011110110010
[KEY_FORWARD][T]0[D]11011110110100
[KEY_REWIND][T]0[D]11011110100100
[KEY_FASTFORWARD][T]0[D]11011110011110
[KEY_VOLUMEUP][T]0[D]11011110010000
[KEY_VOLUMEDOWN][T]0[D]11011110010001
[KEY_CHANNELUP][T]0[D]11011110100000
[KEY_CHANNELDOWN][T]0[D]11011110100001
[KEY_MUTE][T]0[D]11011110001111
[KEY_CHANNEL][T]0[D]11011110010010
[KEY_EXIT][T]0[D]11011110011111
[KEY_MENU][T]0[D]11011110001101
[KEY_LEFT][T]0[D]11011110010110
[KEY_RIGHT][T]0[D]11011110010111
[KEY_UP][T]0[D]11011110010100
[KEY_DOWN][T]0[D]11011110010101
[KEY_OK][T]0[D]11011110100101
[KEY_POWER][T]0[D]11011110111101
[KEY_GOTO][T]0[D]11011110111011
[KEY_TV][T]0[D]11011110011100
[KEY_VIDEOS][T]0[D]11011110011000
[KEY_MUSIC][T]0[D]11011110011001
[KEY_PICTURES][T]0[D]11011110011010
[KEY_GUIDE][T]0[D]11011110011011
[KEY_RADIO][T]0[D]11011110001100
[KEY_STOP][T]0[D]11011110110110

BIN
roles/pi-irserver/files/irserver Executable file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,17 @@
downloaded from
http://www.irtrans.de/de/download/linux.php
build on raspberry pi using
make arm_noccfgcc
-> linker errors with libzip
-> installed libzip-dev and added lzip to linker parameters
mkdir tmp
cd tmp
tar xf ../irserver-src.tar.gz
mkdir arm
# in makefile add: LDFLAGS = -lzip
make irserver_arm_noccf

View File

@@ -0,0 +1,10 @@
[Unit]
Description=IR server for IR remotes
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/irserver /dev/ttyUSB0
[Install]
WantedBy=multi-user.target

Binary file not shown.

View File

@@ -0,0 +1,16 @@
---
- name: Copy irserver
copy: src=irserver dest=/usr/bin/irserver mode=u+rx
- name: Make config dir for remotes
file: path=/usr/bin/remotes state=directory
- name: Copy hauppauge remote
copy: src=hauppauge.rem dest=/usr/bin/remotes/
- name: Copy irserver systemd file
copy: src=irserver.service dest=/lib/systemd/system/
- name: Enable irserver autostart
systemd: name=irserver state=restarted enabled=yes daemon_reload=yes
- name: Add irserver to sysdweb
include_role:
name: pi-sysdweb
vars:
sysdweb_name: irserver

32
roles/pi-knxd/README Normal file
View File

@@ -0,0 +1,32 @@
Infos zum Stick
http://busware.de/tiki-index.php?page=TUL
Blog mit Tutorial:
https://www.meintechblog.de/2018/07/tul-stick-als-knx-ip-gateway-auf-dem-raspberry-pi-einrichten-mit-knxd/
Programmieren
--------------
apt-get -y install dfu-programmer
Beim Einstecken des Sticks programmiertaste auf unterseite gedrueckt halten
sudo dfu-programmer atmega32u4 erase && sudo dfu-programmer atmega32u4 flash TPUARTtransparent.hex && sudo dfu-programmer atmega32u4 reset && sudo reboot
udev-rules
----------
udevadm info -a -n /dev/ttyACM0 | grep '{idProduct}' | head -n1
udevadm info -a -n /dev/ttyACM0 | grep '{idVendor}' | head -n1
udevadm info -a -n /dev/ttyACM0 | grep '{serial}' | head -n1
# my KNX stick
SUBSYSTEM=="tty", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="204b", ATTRS{serial}=="8543934393935171A0B1", SYMLINK+="ttyKNX"
# z-wave stick
SUBSYSTEM=="tty", ATTRS{idVendor}=="0658", ATTRS{idProduct}=="0200", SYMLINK+="ttyZWave"
# HomeMatic stick (for fhem)
SUBSYSTEM=="tty", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="204b", ATTRS{product}=="CUL868", SYMLINK+="ttyHomeMatic"

View File

@@ -0,0 +1,255 @@
:1000000093C00000ACC00000AAC00000A8C000005F
:10001000A6C00000A4C00000A2C00000A0C0000054
:100020009EC000009CC0000008C40000CBC30000BC
:1000300096C0000094C0000092C0000090C0000074
:100040008EC000008CC000008AC0000088C0000084
:1000500086C0000084C0000082C0000080C0000094
:100060007EC0000081C000007AC0000078C000009F
:1000700076C0000074C0000072C0000070C00000B4
:100080006EC000006CC000006AC0000068C00000C4
:1000900066C0000064C0000062C0000060C00000D4
:1000A0005EC000005CC000005AC0000012011001D8
:1000B00002000008EB034B2001000102DC010902F1
:1000C0003E00020100C032090400000102020100EA
:1000D000052400100104240206052406000107057A
:1000E00082030800FF09040100020A00000007055E
:1000F0000402100001070583021000010403090433
:1001000018036200750073007700610072006500DB
:100110002E006400650000003003540050005500BC
:10012000410052005400000011241FBECFEFDAE05E
:10013000DEBFCDBF11E0A0E0B1E0E8EBFFE002C020
:1001400005900D92AC31B107D9F712E0ACE1B1E006
:1001500001C01D92A034B107E1F76FD02BC750CF7B
:1001600088980895889808951F920F920FB60F925D
:1001700011242F933F938F939F93CF93DF93EF930C
:10018000FF939091CE008EB38430D9F4C8EAD1E0C9
:10019000E881F98190838881998101969983888388
:1001A0002E813F818217930721F48C819D81998351
:1001B00088832FB7F8948A859B8501969B878A87C9
:1001C0002FBFFF91EF91DF91CF919F918F913F9141
:1001D0002F910F900FBE0F901F901895089580E0FB
:1001E00091E03CC680E091E012C684B7877F84BF6F
:1001F00088E10FB6F89480936000109260000FBE03
:1002000080E090E020E80FB6F8942093610080939E
:1002100061000FBE809A8898B8D287E690E09093EC
:10022000CD008093CC0086E28093CA0082E0809368
:10023000C80088E98093C9000895EF92FF920F9358
:100240001F93CF93DF93D1DFECE1F1E02FB7F89468
:1002500088E291E091838083938382839583848372
:1002600088EA91E09783868380E890E09187808791
:10027000138612862FBFE8EAF1E02FB7F89484EBDB
:1002800091E091838083938382839583848384E345
:1002900092E09783868380E890E091878087138639
:1002A00012862FBF889878940CE111E088EAE82E36
:1002B00081E0F82E8EB391B39E7F20E0843009F464
:1002C00021E0922B91BB8FB7F894209126013091B9
:1002D00027018FBF809124019091250128173907AC
:1002E00001F180E091E0E8D497FD1BC08898E80117
:1002F000E881F98180838881998101969983888337
:100300002E813F818217930721F48C819D819983EF
:1003100088832FB7F8948A859B8501969B878A8767
:100320002FBF8FB7F894C091B201D091B3018FBFA6
:10033000209721F18898E091AA01F091AB0180E02B
:1003400091E060813FD58823C9F4F70182819381D0
:10035000019693838283268137818217930721F444
:1003600084819581938382832FB7F89482859385C6
:100370000197938782872FBF2197E9F62FB7F894CB
:1003800080912601909127012FBF892BF1F0889849
:10039000E801EA81FB813191FB83EA838E819F81B1
:1003A000E817F90721F48C819D819B838A832FB7FD
:1003B000F8948A859B8501979B878A872FBF8091B8
:1003C000C80085FFFCCF3093CE0080E091E0E3D4FD
:1003D0005FD470CFDA01923049F0933061F0913000
:1003E000F9F4ECEAF0E022E130E01EC0EEEBF0E0E0
:1003F0002EE330E019C0813049F0813018F08230AE
:1004000079F408C0ECEFF0E0849107C0E0E0F1E09F
:10041000849103C0E8E1F1E08491282F30E004C02A
:10042000E0E0F0E020E030E0ED93FC93C9010895B6
:100430009C0140913E0250913F024617570718F425
:10044000F90190E044C06115710511F0AB01F8CFDE
:100450008091E8008E778093E80040E050E0F0CF94
:100460008EB3882309F444C0853009F443C08091D9
:10047000E80083FF02C081E008958091E80082FDDA
:1004800031C08091E80080FF22C08091F3009091FC
:10049000F200782F60E0292F30E0262B372B07C0A1
:1004A00081918093F100415050402F5F3F4F4115A3
:1004B000510519F02830310598F390E028303105C6
:1004C00009F491E08091E8008E778093E80041156F
:1004D000510531F6992321F605C08EB3882341F0EA
:1004E000853041F08091E80082FFF7CF80E00895E9
:1004F00082E0089583E008959C0140913E0250916E
:100500003F024617570718F4F90190E045C06115FE
:10051000710511F0AB01F8CF8091E8008E778093E0
:10052000E80040E050E0F0CF8EB3882309F446C0E5
:10053000853009F445C08091E80083FF02C081E066
:1005400008958091E80082FD33C08091E80080FF2B
:1005500023C08091F3009091F200782F60E0292F62
:1005600030E0262B372B08C084918093F100319620
:10057000415050402F5F3F4F4115510519F0283031
:10058000310590F390E02830310509F491E0809135
:10059000E8008E778093E8004115510529F69923EC
:1005A00009F0C2CF05C08EB3882341F0853041F0F9
:1005B0008091E80082FFF7CF80E0089582E00895FF
:1005C00083E008958F708093E9008091EB00816053
:1005D0008093EB001092ED006093EC004093ED00EF
:1005E0008091EE00881F8827881F08950F931F931E
:1005F000CF93DF93062FEC0110E02AC09881992356
:1006000029F16B81E981FA812C81892F8F708730E4
:1006100018F5223010F452E001C056E040E028E026
:1006200030E003C04F5F220F331F2E173F07D0F378
:100630006295660F660F607C991F9927991F692B39
:100640004295407F452BBEDF882331F01F5F259602
:100650001017A0F281E001C080E0DF91CF911F91DF
:100660000F9108958091380288238CF403C08EB3D3
:100670008823B1F08091E80082FFF9CF8091E800F3
:100680008B778093E80008958EB3882349F080919A
:10069000E80080FFF9CF8091E8008E778093E80032
:1006A00008954091E4005091E50024E68091EC002B
:1006B00080FF23C08091E80080FD1DC08EB3882399
:1006C00011F482E00895853011F483E0089580915B
:1006D000EB0085FF02C081E008958091E4009091D5
:1006E000E5008417950711F3222311F484E008959F
:1006F0002150AC01DBCF80E008958091E80082FFBB
:10070000DDCFF9CFEF92FF920F931F934ED055D0CC
:1007100008ED10E0F80180818F7780838081806808
:10072000808380818F7D808319BC1EBA1092340231
:10073000109236021092350280EEE82EF12CF7016D
:1007400080818B7F8083F80180818160808380E05D
:1007500060E042E037DFE1EEF0E080818E7F808371
:10076000E2EEF0E080818160808380818860808318
:10077000F70180818E7F8083F80180818061808392
:100780001F910F91FF90EF900895E8EDF0E08081C8
:100790008F7E8083E7EDF0E080818160808384E05C
:1007A00082BF81E080933702ADCFE8EDF0E0808139
:1007B0008E7F80831092E20008951092DA001092EA
:1007C000E10008951F920F920FB60F9211241F930C
:1007D0002F933F934F935F936F937F938F939F9349
:1007E000AF93BF93EF93FF931091E9008091EC00DA
:1007F0001092E9008091F000877F8093F000789458
:10080000E0D01092E9008091F00088608093F000C1
:100810001F701093E900FF91EF91BF91AF919F91ED
:100820008F917F916F915F914F913F912F911F9188
:100830000F900FBE0F901F9018951F920F920FB63A
:100840000F9211242F933F934F935F936F937F9356
:100850008F939F93AF93BF93EF93FF938091E100AA
:1008600082FF0AC08091E20082FF06C08091E10011
:100870008B7F8093E1000BD28091DA0080FF1BC058
:100880008091D80080FF17C08091DA008E7F80931E
:10089000DA008091D90080FF0BC080E189BD82E140
:1008A00089BD09B400FEFDCF81E08EBB59DC03C0D9
:1008B00019BC1EBA57DC8091E10080FF17C08091FF
:1008C000E20080FF13C08091E2008E7F8093E200FF
:1008D0008091E20080618093E2008091D800806284
:1008E0008093D80019BC85E08EBBD1D18091E10006
:1008F00084FF2CC08091E20084FF28C080E189BD84
:1009000082E189BD09B400FEFDCF8091D8008F7DC2
:100910008093D8008091E1008F7E8093E1008091E8
:10092000E2008F7E8093E2008091E20081608093FC
:10093000E20080913402882331F48091E30087FD46
:1009400002C081E001C084E08EBBA1D18091E100B2
:1009500083FF26C08091E20083FF22C08091E100E6
:10096000877F8093E10082E08EBB109234028091F9
:10097000E1008E7F8093E1008091E2008E7F809382
:10098000E2008091E20080618093E20080E060E01C
:1009900042E018DE8091F00088608093F00077D10B
:1009A000FF91EF91BF91AF919F918F917F916F9147
:1009B0005F914F913F912F910F900FBE0F901F901D
:1009C00018951F93DF93CF93CDB7DEB7AC970FB6D3
:1009D000F894DEBF0FBECDBFE8E3F2E08091F100F6
:1009E000819322E0E034F207C9F7F9DB8091E80057
:1009F00083FF35C18091380230913902353009F4D6
:100A000087C0363040F43130C9F1313070F03330C6
:100A100009F025C133C0383009F4F4C0393009F485
:100A200003C1363009F01BC195C0803821F08238EF
:100A300009F015C108C090913502809136028823D3
:100A400099F0926011C080913C028F708093E90010
:100A50008091EB0090E025E0969587952A95E1F747
:100A6000982F91701092E9008091E800877F809321
:100A7000E8009093F1001092F100CFC0882319F0A4
:100A8000823009F0ECC090E08F719070009721F0F7
:100A9000029709F0E4C00CC080913A02813009F05D
:100AA000DEC010923602333069F5809336022AC0D8
:100AB00080913A02882331F520913C022F7009F48D
:100AC000CEC02093E9008091EB0080FF1BC0333043
:100AD00021F48091EB00806213C08091EB00806173
:100AE0008093EB0081E090E002C0880F991F2A9567
:100AF000E2F78093EA001092EA008091EB008860B0
:100B00008093EB001092E9008091E800877F88C015
:100B1000882309F0A4C010913A021F778091E30066
:100B20008078812B8093E3008091E800877F809319
:100B3000E80098DD8091E80080FFFCCF8091E30021
:100B400080688093E300112311F482E001C083E008
:100B50008EBB85C08058823008F081C080913A02F7
:100B600090913B0223E08C3D920709F033C083E073
:100B70008C838AE28B837FB7F894DE0115966EE052
:100B800040E050E011E2E62FF0E01093570084912E
:100B900040FF03C082958F706F5F8F70282F30E009
:100BA0008A3018F0C901C79602C0C901C0968D935A
:100BB0009D934F5F5F4F4431510529F77FBF80916F
:100BC000E800877F8093E800CE0103966AE270E038
:100BD0002FDC12C060913C02AE014F5F5F4FFADB29
:100BE000BC01009709F43BC08091E800877F8093A7
:100BF000E80089819A8180DC8091E8008B7780937E
:100C0000E8002DC0803859F58091E800877F8093F7
:100C1000E800809134028093F1008091E8008E77A3
:100C20008093E8001FDD1BC08823C9F490913A022D
:100C30009230A8F48091E800877F8093E800909339
:100C4000340210DD80913402882331F48091E30076
:100C500087FD02C081E001C084E08EBBC3DA8091D1
:100C6000E80083FF0AC08091E800877F8093E80056
:100C70008091EB0080628093EB00AC960FB6F89405
:100C8000DEBF0FBECDBFCF91DF911F9108950895B4
:100C90001F938EB3882371F01091E9008091EC00CE
:100CA0001092E9008091E80083FF01C08ADE1F7086
:100CB0001093E9001F910895FC018EB3843099F5DB
:100CC00084899589A689B7890097A105B10559F14D
:100CD00086818F708093E9008091E80082FF23C0B5
:100CE0004091F3002091F200942F80E030E0822BBD
:100CF000932B892B19F46FEF7FEF04C08091F100E3
:100D0000682F70E04091F3002091F200942F80E072
:100D100030E0822B932B892B41F48091E8008B7774
:100D20008093E80002C06FEF7FEFCB010895089534
:100D3000FC018EB3843051F584899589A689B789E1
:100D40000097A105B10511F181818F708093E900B1
:100D50004091F3002091F200942F80E030E0822B4C
:100D6000932B892BA9F09091E8008091E8008E7771
:100D70008093E80095FD0CC094DC982F882349F4FB
:100D80008091E8008E778093E80003C092E001C074
:100D900090E0892F0895FC018EB3843091F484890A
:100DA0009589A689B7890097A105B10551F0818180
:100DB0008F708093E9008091E80080FF02C0CF012E
:100DC000B7CF08951F93FC01162F8EB38430E1F442
:100DD00084899589A689B7890097A105B105A1F0F5
:100DE00081818F708093E9008091E80085FD08C0C3
:100DF0008091E8008E778093E80053DC882329F403
:100E00001093F10080E001C082E01F910895CF931C
:100E1000DF93EC014096FC018BE0DF011D928A9587
:100E2000E9F782E08C83898783E08E87CE01019683
:100E300061E0DCDB882371F0CE01069661E0D6DB51
:100E4000882341F0CE010B9661E0D0DB882319F0B6
:100E500081E001C080E0DF91CF910895CF93DF93CF
:100E6000EC018091E80083FFA2C0888190E020918E
:100E70003C0230913D022817390709F098C0809153
:100E80003902813269F0823220F4803209F08FC059
:100E90003CC0823209F46AC0833209F088C079C04C
:100EA00080913802813A09F082C08091E800877F02
:100EB0008093E8008091E80080FFFCCF8C899D89B9
:100EC000AE89BF898093F100292F3A2F4B2F5527E8
:100ED0002093F1009D01442755272093F1008B2F8B
:100EE0009927AA27BB278093F100888D8093F10072
:100EF000898D8093F1008A8D8093F1008091E800C4
:100F00008E778093E800AEDB52C080913802813248
:100F100009F04DC08091E800877F8093E80004C00D
:100F20008EB3882309F443C08091E80082FFF8CF94
:100F30008091F1009091F1002091F1003091F10049
:100F40008C8B9D8B2E8B3F8B8091F100888F8091B5
:100F5000F100898F8091F1008A8F8091E8008B7772
:100F60008093E8007FDBCE0139D921C0809138021F
:100F70008132E9F48091E800877F8093E80072DB9A
:100F800080913A0290913B02998B888BCE01CFDE03
:100F90000EC080913802813251F48091E800877F41
:100FA0008093E8005FDBCE0160913A02C0DEDF9102
:080FB000CF910895F894FFCFE2
:100FB80000831000000104100000018208000001F5
:0C0FC8000000000000000000000000001D
:00000001FF

View File

@@ -0,0 +1,8 @@
#!/bin/bash
apt-get -y install dfu-programmer
dfu-programmer atmega32u4 erase
dfu-programmer atmega32u4 flash TPUARTtransparent.hex
dfu-programmer atmega32u4 reset

View File

@@ -0,0 +1,2 @@
include "hauppauge.conf"
include "small_led_remote.conf"

View File

@@ -0,0 +1,350 @@
begin remote
name small_led_remote
flags RAW_CODES|CONST_LENGTH
eps 30
aeps 100
gap 108076
begin raw_codes
name ON
9257 4452 619 534 611 510
637 509 611 535 611 510
663 485 609 535 635 486
635 1637 640 1610 613 1636
641 1637 637 485 663 1611
615 1637 613 1637 666 1612
639 1612 615 537 608 509
661 486 609 535 636 485
637 510 609 535 611 510
662 1628 599 1636 640 1612
642 1637 639 1613 615 1637
642
name OFF
9068 4464 602 539 597 511
625 511 598 537 599 511
625 512 597 537 599 513
624 1643 599 1640 604 1640
630 1641 603 511 626 1642
603 1641 627 1618 630 511
599 1641 632 512 599 538
598 512 626 511 599 539
598 512 625 1641 603 511
626 1642 604 1641 603 1641
655 1617 603 1641 604 1640
629
name RED
9237 4461 602 547 597 519
625 521 597 545 600 518
626 520 598 547 596 521
624 1646 603 1646 602 1645
631 1647 601 524 621 1645
605 1645 604 1647 629 522
595 548 596 1645 604 550
594 525 620 520 597 548
596 523 622 1649 601 1647
602 547 599 1643 606 1644
631 1645 604 1645 604 1645
631
name GREEN
9103 4437 624 513 622 487
648 487 622 512 622 487
648 487 622 513 621 487
652 1616 621 1619 623 1617
651 1617 624 486 649 1618
624 1616 624 1617 651 1617
625 486 649 1618 624 486
649 486 622 512 623 486
648 486 622 512 623 1617
625 511 623 1618 623 1618
651 1617 624 1618 625 1617
651
name BLUE
9024 4501 609 524 613 494
638 498 610 522 611 498
634 501 610 523 613 496
635 1632 563 1680 560 1680
640 1629 609 499 675 1594
646 1596 620 1622 647 488
619 1621 647 1621 620 489
645 490 618 517 613 495
640 494 562 1679 589 547
562 572 564 1677 563 1679
589 1678 610 1632 608 1634
641
name WHITE
9083 4471 590 547 586 523
614 522 587 548 588 520
614 521 590 545 588 520
616 1650 591 1651 590 1652
618 1650 591 521 615 1649
592 1651 592 1649 618 1650
591 1650 591 1651 616 521
588 547 586 523 614 524
584 550 586 520 615 520
587 547 587 1652 589 1655
616 1650 589 1653 591 1655
615
name 1_ORANGE
9076 4445 562 581 600 513
618 515 595 530 554 553
623 514 596 532 600 509
581 1683 604 1637 601 1637
633 1636 604 502 584 1710
577 1638 555 1686 580 563
544 584 598 512 619 1638
556 563 571 563 543 592
588 510 623 1637 603 1636
556 1687 580 559 548 1682
584 1686 555 1684 556 1684
583
name 4_ORANGE
9158 4444 620 517 621 490
648 490 620 517 621 490
647 491 620 517 621 490
647 1624 620 1622 623 1622
651 1622 624 491 647 1622
623 1621 623 1622 650 491
620 518 620 1622 622 1623
648 493 619 519 619 493
621 518 593 1647 629 1644
596 517 621 516 593 1648
624 1646 598 1647 597 1647
625
name 7_YELLOW
9092 4468 593 543 592 518
619 517 592 544 593 515
621 518 592 543 592 518
620 1647 593 1650 596 1648
621 1647 596 517 619 1647
601 1642 595 1648 623 516
593 543 593 517 620 516
592 1649 621 518 591 544
594 516 619 1647 595 1662
582 1646 622 1647 596 517
618 1647 595 1648 595 1649
621
name STAR_YELLOW
9012 4468 589 544 587 518
613 518 588 543 586 519
615 516 587 544 586 522
608 1651 588 1650 588 1650
613 1650 588 520 612 1649
588 1649 588 1650 614 520
587 547 583 1649 588 543
589 1650 588 545 587 517
613 518 587 1649 614 1651
586 518 613 1650 589 517
611 1652 587 1650 588 1650
613
name 2_GREEN
9022 4497 569 566 561 546
587 546 561 573 561 547
587 547 561 571 562 546
589 1679 562 1677 564 1677
591 1678 564 546 588 1679
563 1678 564 1678 639 1631
565 546 588 546 561 1678
638 497 610 525 604 501
637 495 613 521 560 1679
563 1678 590 549 609 1626
589 1678 606 1636 561 1678
590
name 8_LIGHT_BLUE
9062 4473 589 548 587 521
615 520 586 548 588 519
614 521 586 547 587 520
615 1650 588 1653 589 1651
616 1651 588 522 612 1653
585 1655 536 1704 608 1659
537 572 561 572 533 600
546 1691 536 599 534 573
560 573 533 600 531 1706
581 1659 612 1654 586 522
609 1655 587 1653 585 1653
615
name 0_LIGHT_BLUE
9001 4491 564 569 563 543
589 542 563 569 563 543
589 542 563 569 563 542
589 1674 565 1674 565 1674
590 1674 564 543 588 1675
562 1676 535 1704 560 1703
535 572 560 1704 535 572
559 1703 535 574 557 572
533 599 533 580 552 1703
535 570 561 1703 535 572
560 1703 535 1703 535 1703
561
name 3_LIGHT_BLUE
9031 4460 596 536 596 510
622 510 595 537 594 510
622 510 618 514 593 511
621 1643 595 1643 596 1643
621 1643 595 519 612 1644
595 1643 595 1644 622 512
593 1645 620 514 591 1644
622 513 592 539 593 513
619 513 592 1644 621 513
592 1643 622 512 593 1643
622 1642 597 1642 597 1642
623
name 6_PURPLE
9015 4472 589 545 586 519
611 521 589 543 584 522
611 526 577 549 585 519
614 1650 588 1651 590 1649
614 1651 588 519 613 1649
590 1648 586 1654 613 521
584 1653 613 1652 586 1654
586 547 586 520 613 520
584 547 585 1652 588 545
585 521 616 516 586 1652
610 1655 586 1651 588 1651
614
name HASH_PINK
9048 4457 600 538 594 508
625 508 598 534 598 509
625 508 598 535 598 508
624 1638 600 1639 600 1640
627 1638 600 508 624 1641
599 1638 600 1639 626 509
597 1639 626 1640 600 509
623 1639 599 509 624 509
597 535 597 1639 600 535
597 509 623 1639 600 509
623 1639 600 1639 599 1640
626
name 5_BLUE
9065 4404 618 505 619 505
618 506 620 505 618 505
620 505 619 505 621 503
619 1623 620 1622 620 1621
620 1622 620 504 619 1623
620 1623 620 1623 617 1626
619 505 619 1623 620 1622
620 505 619 505 614 510
618 506 618 505 616 1624
619 504 620 504 619 1622
619 1623 618 1624 618 1623
617
name 9_PINK
9047 4402 616 507 618 504
619 504 618 505 618 505
615 508 618 504 619 504
618 1622 618 1622 619 1622
619 1624 618 504 618 1623
618 1623 619 1622 619 505
618 1623 619 504 618 505
620 1622 618 505 619 505
619 502 619 1622 619 506
619 1621 620 1623 618 504
619 1622 619 1622 617 1624
619
name FLASH
9066 4406 637 486 638 485
640 484 637 487 637 485
637 486 638 485 635 488
641 1602 640 1601 633 1608
634 1608 631 492 633 1609
633 1609 634 1607 631 1609
631 1610 630 493 632 1609
638 485 631 491 636 487
637 487 635 487 638 486
630 1611 638 484 632 1609
641 1599 637 1604 629 1612
638
name BRIGHTNESS_DOWN
9059 4403 619 504 619 505
619 506 618 505 618 505
619 504 618 505 619 504
620 1623 617 1624 618 1624
619 1622 619 505 619 1623
618 1623 619 1623 619 1623
619 505 619 505 619 505
617 507 619 503 619 504
619 506 616 507 616 1625
618 1624 619 1623 618 1624
618 1625 618 1623 619 1623
618
name BRIGHTNESS_UP
9055 4404 618 504 619 504
619 504 619 505 618 505
618 505 617 506 618 505
618 1623 619 1622 619 1624
618 1623 619 505 618 1622
619 1623 616 1625 619 505
618 504 619 505 619 504
616 507 618 505 619 504
618 505 618 1623 616 1627
615 1625 618 1623 619 1623
618 1624 618 1622 619 1622
618
name STROBE
9052 4403 618 504 619 504
618 506 615 507 618 504
619 504 619 504 619 504
619 1622 617 1624 619 1622
618 1623 619 504 617 1624
619 1622 619 1623 619 1622
619 1622 619 1622 618 1622
620 504 619 505 618 505
614 509 618 505 614 509
618 504 619 507 614 1625
619 1623 616 1625 619 1622
619
name FADE
9040 4403 616 504 618 504
620 503 618 504 618 505
617 505 616 504 618 504
618 1621 617 1624 614 1625
617 1621 618 504 614 1625
617 1622 618 1622 617 1623
617 1622 618 504 618 504
617 1623 617 504 615 507
616 505 618 503 618 504
618 1622 618 1622 617 505
618 1623 617 1623 617 1622
617
name SMOOTH
9040 4401 619 504 618 504
618 504 617 505 618 503
618 503 618 503 617 505
617 1623 616 1623 618 1623
616 1624 617 505 617 1622
617 1622 616 1624 618 1621
617 1623 614 1623 619 503
617 1622 618 504 618 504
617 504 618 504 617 505
617 505 616 1623 618 504
617 1623 616 1622 618 1621
618
end raw_codes
end remote

View File

@@ -0,0 +1,26 @@
---
- name: Install lirc
apt:
name: lirc
- name: Install config file lirc_options.conf
copy: src=lirc_options.conf dest=/etc/lirc/lirc_options.conf
- name: Install config file lircd.conf
copy: src=lircd.conf dest=/etc/lirc/lircd.conf
- name: Install remote file
copy: src=hauppauge.conf dest=/etc/lirc/hauppauge.conf
- name: Activate overlay in boot config
lineinfile:
path: /boot/firmware/config.txt
regexp: "^#?dtoverlay=gpio-ir"
line: "dtoverlay=gpio-ir,gpio_pin=17"
register: boot_overlay
- name: Restart lircd
systemd: name=lircd state=started enabled=yes daemon_reload=yes
- name: Reboot if boot overlay changed
reboot:
when: boot_overlay.changed
- name: Add to sysdweb
include_role:
name: pi-sysdweb
vars:
sysdweb_name: lircd

View File

@@ -0,0 +1,40 @@
general:
alsa_device: softvol_effects
mqtt:
user: musicmouse
password: KNLEFLZF94yA6Zhj141
server: homeassistant
hass_url: https://ha.bauer.tech
hass_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI4N2ExMzM2ZmQ4ZWQ0ZDgzOWZhMjU3NmZjYTg1NWQ1ZiIsImlhdCI6MTcwNDAyOTUyOSwiZXhwIjoyMDE5Mzg5NTI5fQ.vx9L5bTSey98uc8TwodShaEXSMr-hjXugPsNviR_fEw"
button_leds_brightness: 0.5
volume_increment: 5
min_volume: 23
max_volume: 70
serial_port: "/dev/ttyUSB0"
figures:
elefant:
id: "88041174e9"
colors: ["#ffff00", "#00c8ff", "#094b46", "#c20099"]
fuchs:
id: "8804ce7230"
colors: ["#F4D35E", "#F95738", "#F95738", "#083d77"]
eule:
id: "88040d71f0"
colors: ["#e5a200", "#f8e300", "w33", "w99"]
omnom:
id: "88043c6ede"
colors: ["#005102", "#fec800", "#005102", "#3bc405"]
eichhoernchen:
id: "88040b78ff"
colors: ["#ff0ada", "#4BC6B9", "#69045a", "#4BC6B9"]
hund:
id: "8804bc7444"
colors: ["#ffff00", "#00c8ff", "#094b46", "#c20099"]
hase:
id: "88044670ba"
colors: ["#ffff00", "#00c8ff", "#094b46", "#c20099"]
schneemann:
id: "88043f71c2"
colors: ["#ff0ada", "#4BC6B9", "#69045a", "#4BC6B9"]

View File

@@ -0,0 +1,11 @@
[Unit]
Description=MusicMouse Player
After=network.target
[Service]
Type=simple
Restart=always
ExecStart=/opt/musicmouse_venv/bin/python /opt/musicmouse/espmusicmouse/host_driver/main.py /media/musicmouse/
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,15 @@
[global]
workgroup = WORKGROUP
logging = syslog@1
server role = standalone server
obey pam restrictions = no
unix password sync = no
[MusicMouse]
browseable = yes
path = /media/musicmouse
guest ok = no
writeable = yes
create mask = 666
force create mode = 666

View File

@@ -0,0 +1,38 @@
---
- name: Packages
apt:
name:
- python3
- python3-pip
- python3-vlc # also in venv - but this installs vlc + deps
- samba
- name: Checkout Musicmouse repo
ansible.builtin.git:
repo: 'ssh://git@git.bauer.tech:2222/martin/musicmouse.git'
dest: '/opt/musicmouse'
version: 'release/1.1'
accept_hostkey: true
- name: Create and update virtual env
ansible.builtin.pip:
requirements: /opt/musicmouse/espmusicmouse/host_driver/requirements.txt
virtualenv: /opt/musicmouse_venv
virtualenv_command: "/usr/bin/python3 -m venv"
- name: Create media directory
file:
path: /media/musicmouse
state: directory
- name: Install config file
copy: src=config.yml dest=/media/musicmouse/config.yml
- name: Install systemd service file
copy: src=musicmouse.service dest=/etc/systemd/system/
- name: Add script to autostart and start now
systemd: name=musicmouse state=restarted enabled=yes daemon_reload=yes
- name: Samba setup
copy: src=smb.conf dest=/etc/samba/
- name: Restart samba
systemd: name=smbd state=restarted enabled=yes
# manual steps:
# - set samba passwords with smbpasswd
# - copy music into /media/musicmouse (or via samba share)
# - upload host driver?
# - manual patching of hassclient necessary :( loop has to be passed in, to not use running_event_loop

View File

@@ -0,0 +1,3 @@
---
shairport_sync_version: "3.3.5"
shairport_name: Unnamed Raspberry with shairport

View File

@@ -0,0 +1,3 @@
---
dependencies:
- role: pi-alsasetup

View File

@@ -0,0 +1,38 @@
---
- name: Apt install dependencies
apt:
cache_valid_time: 7200
state: present
name:
- build-essential
- git
- xmltoman
- autoconf
- automake
- libtool
- libpopt-dev
- libconfig-dev
- libasound2-dev
- avahi-daemon
- libavahi-client-dev
- libssl-dev
- libsoxr-dev
- name: Build and Install Shairport sync (may take a while)
script: "build-shairport-sync.sh ${shairport_sync_version}"
args:
creates: /usr/local/bin/shairport-sync
- name: Copy config
template: src=shairport-sync.conf dest=/etc/shairport-sync.conf
- name: Modify service file to run as root
lineinfile:
path: /lib/systemd/system/shairport-sync.service
regexp: "^#?User="
line: "User=root"
- name: Restart shairport-sync
systemd: name=shairport-sync state=restarted enabled=yes daemon_reload=yes
- name: Add to sysdweb
include_role:
name: pi-sysdweb
vars:
sysdweb_name: shairport-sync

Binary file not shown.

View File

@@ -0,0 +1,36 @@
[Unit]
Description=SiS PM Control for Linux
Wants=network-online.target
After=network-online.target
Wants=systemd-udev-settle.service
After=systemd-udev-settle.service
[Install]
WantedBy=multi-user.target
[Service]
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateTmp=true
PrivateUsers=true
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectSystem=strict
RemoveIPC=true
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
SystemCallFilter=@system-service
SystemCallArchitectures=native
UMask=177
Type=forking
ExecStart=/usr/bin/sispmctl -p 2638 -l
SyslogIdentifier=sispmctl
Restart=always
RestartSec=5

View File

@@ -0,0 +1,21 @@
---
- name: Check if sispmctl already exists
stat: path=/usr/bin/sispmctl
register: sispmctl_file
- name: Install dependencies
apt: name="libusb-dev" cache_valid_time=7200 state=present
- name: Copy sispmctl sources
unarchive: src=sispmctl-4.7.tar.gz dest=/tmp
when: sispmctl_file.stat.exists == false
- name: Build and install
shell: cd /tmp/sispmctl*/ && ./configure --prefix=/usr && make install
when: sispmctl_file.stat.exists == false
- name: Install systemd service file
copy: src=sispmctl.service dest=/lib/systemd/system/
- name: Add script to autostart and start now
systemd: name=sispmctl state=started enabled=yes daemon_reload=yes
- name: Add to sysdweb
include_role:
name: pi-sysdweb
vars:
sysdweb_name: sispmctl

View File

@@ -0,0 +1,47 @@
Repository: https://github.com/ralph-irving/squeezelite.git
Version: c89faf3280c5dca3f04906a4ff7796a0043e99f0
Packages to build:
- libmad0
- libmpg123-0
- libflac8
- libvorbisfile3
- libfaad2
# for building libssl-dev, libasound2-dev, libflac-dev, libvorbis-dev, libsoxr-dev, libfaad-dev, libmad0-dev, libmpg123-dev
Adapted makefile.rpi
#OPTS = -DRESAMPLE -DDSD -DUSE_SSL -DLINKALL -I./include -I./include/opus -I./include/alac -I/usr/local/include -s -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s
Old
---
Repository: https://github.com/ralph-irving/squeezelite.git
Version: 779fe9035a2dfcaeeb5335497a012d5e7241a409
Custom Makefile based on Makefile.rpi:
```Makefile
OPTS = -DLINUX -DUSE_SSL -DLINKALL -I./include -I./include/opus -I./include/alac -I/usr/local/include -s -march=armv6 -mfloat-abi=hard -mfpu=vfp
LDFLAGS=-L./lib -L/usr/local/lib -s -lgomp -lasound
include Makefile
```
Required packages:
```bash
apt-get install libasound2 libasound2-dev libflac-dev libmad0-dev libfaad-dev libmpg123-dev libvorbis-dev
```
- no RPI switch (this is only when wanting to do switiching based on GPIO headers)
- manually added -lasound (don't know why it wasn't in there)
Then just build with
make -f mymakefile (on a raspi)

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,31 @@
---
- name: Uninstall system package of squeezelite
apt: name=squeezelite state=absent
- name: Install dependencies
apt:
name:
- libmad0
- libmpg123-0
- libflac12
- libvorbisfile3
- libfaad2
# for building libssl-dev, libasound2-dev, libflac-dev, libvorbis-dev, libsoxr-dev, libfaad-dev, libmad0-dev, libmpg123-dev
state: present
cache_valid_time: 7200
- name: Remove old config file if present
file: path=/etc/default/squeezelite state=absent
- name: Copy over custom compile version of squeezelite
copy: src=squeezelite dest=/opt/squeezelite mode=700
- name: Install systemd service file
template: src=squeezelite.service dest=/lib/systemd/system/
- name: Enable sysdweb autostart
systemd: name=squeezelite state=restarted enabled=yes daemon_reload=yes
- name: Add to sysdweb
include_role:
name: pi-sysdweb
vars:
sysdweb_name: squeezelite
# build with adapted Makefile.rpi:
#OPTS = -DRESAMPLE -DDSD -DUSE_SSL -DLINKALL -I./include -I./include/opus -I./include/alac -I/usr/local/include -s -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Squeezelite
After=network.target
[Service]
ExecStart=/opt/squeezelite -n {{squeezelite_name}} -s {{squeezeserver}} -o softvol_squeezelite
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,2 @@
---
squeezeserver: 192.168.178.80

View File

@@ -0,0 +1,3 @@
---
- name: restart-squeezelite
systemd: name=squeezelite state=restarted enabled=yes daemon_reload=yes

View File

@@ -0,0 +1,3 @@
---
dependencies:
- role: pi-alsasetup

View File

@@ -0,0 +1,13 @@
---
- name: Apt install squeezelite package
apt: name=squeezelite cache_valid_time=7200 state=present
notify: restart-squeezelite
- name: Install config file
template: src=squeezelite.cfg dest=/etc/default/squeezelite
notify: restart-squeezelite
- name: Add to sysdweb
include_role:
name: pi-sysdweb
vars:
sysdweb_name: squeezelite

View File

@@ -1,3 +1,3 @@
SL_NAME="{{squeezelite_name}}"
SL_SOUNDCARD="softvol_squeezelite"
SB_SERVER_IP="192.168.178.80"
SB_SERVER_IP="{{squeezeserver}}"

View File

@@ -0,0 +1,2 @@
#http://downloads.slimdevices.com/LogitechMediaServer_v7.9.2/logitechmediaserver_7.9.2_arm.deb
https://downloads.slimdevices.com/LogitechMediaServer_v8.4.0/logitechmediaserver_8.4.0_arm.deb

View File

@@ -0,0 +1,39 @@
---
- name: Install packages required for squeeze server
apt:
name:
- libsox-fmt-all
- libflac-dev
- libfaad2
- libmad0
- perl-openssl-defaults
- libnet-ssleay-perl
- libio-socket-ssl-perl
- nasm
- build-essential
- iptables-persistent
cache_valid_time: 7200
state: present
- name: copy squeezeserver package
copy: src=logitechmediaserver_8.4.0_arm.deb dest=/tmp
- name: install squeezeserver package
apt: deb=/tmp/logitechmediaserver_8.4.0_arm.deb
- name: Enable sysdweb autostart
systemd: name=logitechmediaserver state=started enabled=yes
- name: Add to sysdweb
include_role:
name: pi-sysdweb
vars:
sysdweb_name: logitechmediaserver
- name: Forward port 80 to 9000
iptables:
table: nat
chain: PREROUTING
in_interface: eth0
protocol: tcp
match: tcp
destination_port: "80"
jump: REDIRECT
to_ports: "9000"
comment: Redirect web traffic to port 9000

View File

@@ -0,0 +1,13 @@
---
wifi_ssid: "" # put SSID here to configure wifi
ansible_user: "root" # "User to connect with, put in 'pi' here if you connect the first time, else leave empty"
new_hostname: "" # set this to change the hostname
timezone: "Europe/Berlin"
wifi_country: "DE"
wifi_pass_url: "bauer_wifi" # has to be in keepass with url "wifi_pass_url"
ansible_ssh_pass: "raspberry"
ansible_become_password: "raspberry"
ansible_become: yes

Some files were not shown because too many files have changed in this diff Show More