commit d9cd53477f6bd4dae8f2cc30b9c14021b3af34eb Author: Martin Bauer Date: Wed Jan 8 20:18:23 2020 +0100 Raspberry Pi setup as bluetooth speaker diff --git a/configs/gitconfig b/configs/gitconfig new file mode 100644 index 0000000..5b9da46 --- /dev/null +++ b/configs/gitconfig @@ -0,0 +1,7 @@ +[user] + name = Martin Bauer + email = bauer_martin@gmx.de +[alias] + sf = submodule foreach +[core] + autocrlf = input diff --git a/configs/vimrc b/configs/vimrc new file mode 100644 index 0000000..c5b0096 --- /dev/null +++ b/configs/vimrc @@ -0,0 +1,14 @@ +:colorscheme elflord +:set tabstop=4 +:set shiftwidth=4 +:set expandtab + +"other key mappings +inoremap jk + + +syntax on + +" Treat long lines as break lines (useful when moving around in them) +map j gj +map k gk diff --git a/keepass_plugin.py b/keepass_plugin.py new file mode 100644 index 0000000..913b4ae --- /dev/null +++ b/keepass_plugin.py @@ -0,0 +1,63 @@ +# Copy this to ".ansible/plugins/lookup" +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase +from keepasshttplib import keepasshttplib, encrypter + + +DOCUMENTATION = """ + lookup: keepass + author: Martin Bauer + version_added: '0.2' + short_description: fetch data from KeePass over KeePassHTTP + description: + - This lookup returns a username or password queried by the URL of the keepass entry + options: + _terms: + description: + - first is the URL to search for + - second is a property name of the entry, e.g. username or password + required: True + notes: + - https://github.com/viczem/ansible-keepass + + example: + - "{{ lookup('keepass', 'urlOfEntry', 'password') }}" +""" + + +class LookupModule(LookupBase): + + def __init__(self, *args, **kwargs): + super(LookupModule, self).__init__(*args, **kwargs) + self.k = keepasshttplib.Keepasshttplib() + + def run(self, terms, variables=None, **kwargs): + if not terms or len(terms) > 2: + raise AnsibleError('Keepass wrong request format') + if len(terms) == 1: + entry_path, entry_attr = terms[0], 'password' + else: + entry_path, entry_attr = terms[0], terms[1] + + 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)) + if auth: + if entry_attr not in ('username', 'user', 'pass', 'passwd', 'password'): + raise AnsibleError("Keepass wrong entry") + + ret = auth[0] if entry_attr.startswith('user') else auth[1] + return [ret] + + def _test_connection(self): + key = self.k.get_key_from_keyring() + if key is None: + key = encrypter.generate_key() + id_ = self.k.get_id_from_keyring() + try: + return self.k.test_associate(key, id_) + except requests.exceptions.ConnectionError as e: + raise AnsibleError('Keepass Connection Error: {}'.format(e)) diff --git a/pis/01-download-and-prepare-raspi-image.yml b/pis/01-download-and-prepare-raspi-image.yml new file mode 100644 index 0000000..43e82f2 --- /dev/null +++ b/pis/01-download-and-prepare-raspi-image.yml @@ -0,0 +1,94 @@ +# Run with +# ansible-playbook pis/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: | + The prepared image is ready at {{target_folder}}. + Copy it to sdcard with + dd bs=4M if=the_image of=/dev/your/sdcard diff --git a/pis/02-provision_new_pi.yml b/pis/02-provision_new_pi.yml new file mode 100644 index 0000000..3cb0b2d --- /dev/null +++ b/pis/02-provision_new_pi.yml @@ -0,0 +1,102 @@ +# Run with +# ansible-playbook -i raspberrypi, 02-provision_new_pi.yml +# where "raspberrypi" is the hostname of the pi +--- + +- hosts: all + 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 + when: wifi_ssid | bool + - name: Change WiFi country + command: "raspi-config nonint do_wifi_country {{ wifi_country }}" + when: wifi_ssid | bool + - name: Set WiFi credentials + command: "raspi-config nonint do_wifi_ssid_passphrase {{ wifi_ssid }} {{ lookup('keepass', wifi_pass_url) }}" + when: wifi_ssid | bool + + handlers: + - name: restart sshd + service: + name: sshd + state: restarted + + diff --git a/pis/03-install-pulseaudio-bluetooth.yml b/pis/03-install-pulseaudio-bluetooth.yml new file mode 100644 index 0000000..1e1421e --- /dev/null +++ b/pis/03-install-pulseaudio-bluetooth.yml @@ -0,0 +1,102 @@ +# Run with +# ansible-playbook -i raspberrypi, 03-install-pulseaudio-bluetooth.yml +# where "raspberrypi" is the hostname of the pi +--- + +- hosts: all + gather_facts: false + remote_user: root + vars: + bluetooth_mac_address: B8:27:EB:AA:23:4E + vars_prompt: + - name: bluetooth_name + prompt: Name of Bluetooth device + private: no + tasks: + # --- Initial Checks --- + - name: Detect Raspi Model + slurp: + src: /sys/firmware/devicetree/base/model + register: raspberry_model + - name: Decode Raspi Model + set_fact: + raspi_model: "{{ raspberry_model.content | b64decode }}" + - name: Check if its a Raspberry 3 (where I've tested this) + fail: + msg: I've only tested this on Raspberry 3 Model Pi, this is something else + when: 'not raspi_model.startswith("Raspberry Pi 3 Model B Plus")' + #- name: Determine MAC Address of first Bluetooth device + # command: 'hciconfig hci0' + # register: hci_output + #- name: Extract MAC address + # set_fact: + # hci0_addr: "{{ hci_output.stdout | regex_search('BD Address: ([^\\s]+)', '\\1') | first }}" + # --- The actual work --- + - name: Install pulseaudio packages + apt: + name: + - pulseaudio + - pulseaudio-utils + - pulseaudio-module-bluetooth + - alsa-utils + - bluez-tools + cache_valid_time: 7200 + state: present + install_recommends: no + - name: Add root to pulse-access group + user: + name: root + groups: + - pulse-access + - pulse + - audio + append: yes + - name: Copy DBus permission file + copy: src=dbus-pulseaudio-bluetooth.conf dest=/etc/dbus-1/system.d/pulseaudio-bluetooth.conf + - name: Copy Pulseaudio system-mode config file + copy: src=pulseaudio.cfg dest=/etc/pulse/system.pa + - name: Copy bluetoothd main.conf + copy: src=bluetooth_main.conf dest=/etc/bluetooth/main.conf + - name: Copy bluetoothd audio.conf + copy: src=bluetooth_audio.conf dest=/etc/bluetooth/audio.conf + - name: Copy bt-agent service file + template: src=bt-agent.service dest=/lib/systemd/system/bt-agent.service + - name: Copy pulseaudio service file + copy: src=pulseaudio.service dest=/lib/systemd/system/ + - name: Copy Python script for discoverable and name setting + copy: + src: start_bt_discoverable.py + dest: /bin/start_bt_discoverable + mode: u=rwx,g=rx + #- name: Stop Bluetooth + # service: + # name: bluetooth + # state: stopped + #- name: Set bluetooth device name + # ini_file: + # path: "/var/lib/bluetooth/{{hci0_addr}}/settings" + # section: General + # option: Alias + # value: "{{bluetooth_name}}" + #- name: Mark as discoverable + # ini_file: + # path: "/var/lib/bluetooth/{{hci0_addr}}/settings" + # section: General + # option: Discoverable + # value: true + - name: Enable and restart bluetooth + service: + name: bluetooth + enabled: yes + state: restarted + - name: Enable and restart pulseaudio + service: + name: pulseaudio + enabled: yes + state: restarted + - name: Enable and restart bt-agent + service: + name: bt-agent + enabled: yes + state: restarted + daemon_reload: yes diff --git a/pis/bluetooth_audio.conf b/pis/bluetooth_audio.conf new file mode 100644 index 0000000..6c8d5e0 --- /dev/null +++ b/pis/bluetooth_audio.conf @@ -0,0 +1,9 @@ +# file goes into /etc/bluetooth/audio.conf +# mostly default +# but tried HFP=false and Headset=false to not be detected as "headset" + +[General] +Enable=Source,Sink,Media,Socket +HFP=false +Headset=false +Class=0x20041C diff --git a/pis/bluetooth_main.conf b/pis/bluetooth_main.conf new file mode 100644 index 0000000..b3ee70d --- /dev/null +++ b/pis/bluetooth_main.conf @@ -0,0 +1,11 @@ +# file goes into /etc/bluetooth/main.conf + +[General] +Class=0x20041C # No idea - got this from the internet +DiscoverableTimeout = 0 # stay discoverable and pairable forever +PairableTimeout = 0 + +[GATT] + +[Policy] +AutoEnable=true # was already in default config diff --git a/pis/bt-agent.service b/pis/bt-agent.service new file mode 100644 index 0000000..6bb7c22 --- /dev/null +++ b/pis/bt-agent.service @@ -0,0 +1,18 @@ +[Unit] +Description=bt-agent to accept bluetooth connections without pin +After=bluetooth.service +RestartSec=3 +Restart=always + + +[Service] +Type=simple +Restart=always +RestartSec=1 +User=root +# make first adaptor discoverable +ExecStartPre=/bin/start_bt_discoverable {{bluetooth_mac_address}} {{bluetooth_name}} +ExecStart=bt-agent -c NoInputNoOutput + +[Install] +WantedBy=bluetooth.target diff --git a/pis/dbus-pulseaudio-bluetooth.conf b/pis/dbus-pulseaudio-bluetooth.conf new file mode 100644 index 0000000..a1fc8ee --- /dev/null +++ b/pis/dbus-pulseaudio-bluetooth.conf @@ -0,0 +1,9 @@ +# put this into: /etc/dbus-1/system.d/pulseaudio-bluetooth.conf + + + + + + + + \ No newline at end of file diff --git a/pis/debmatic-install.yml b/pis/debmatic-install.yml new file mode 100644 index 0000000..0f9dc1c --- /dev/null +++ b/pis/debmatic-install.yml @@ -0,0 +1,28 @@ +--- + +- hosts: kitchenpi + tasks: + - name: Add Key for Debmatic Repository + apt_key: + url: https://www.debmatic.de/debmatic/public.key + state: present + - name: Add Debmatic Repository + repo: deb https://www.debmatic.de/debmatic stable main + state: present + filename: debmatic + - name: Install Pre-requesites + apt: + update_cache: yes + name: + - build-essential + - bison + - flex + - libssl-dev + - raspberrypi-kernel-headers + - pivccu-modules-dkms + - name: Reboot + reboot: + - name: Install Debmatic package + apt: + update_cache: no + name: debmatic diff --git a/pis/knxd/how_to_build.md b/pis/knxd/how_to_build.md new file mode 100644 index 0000000..0ec9ea9 --- /dev/null +++ b/pis/knxd/how_to_build.md @@ -0,0 +1,15 @@ +KNXD +---- + +The following commands build knxd. Run these on the raspberry pi! +After these commands *.deb files are in the created build_dir and should be copied back to this folder. + +```bash +mkdir build_dir +cd build_dir +git clone https://github.com/knxd/knxd.git +cd knxd +git checkout stable +apt-get install debhelper cdbs automake libtool libusb-1.0-0-dev git-core build-essential libsystemd-dev dh-systemd libev-dev cmake +dpkg-buildpackage -b -uc +``` diff --git a/pis/knxd/knxd-tools_0.14.29-5_armhf.deb b/pis/knxd/knxd-tools_0.14.29-5_armhf.deb new file mode 100644 index 0000000..13dce4d Binary files /dev/null and b/pis/knxd/knxd-tools_0.14.29-5_armhf.deb differ diff --git a/pis/knxd/knxd_0.14.29-5_armhf.deb b/pis/knxd/knxd_0.14.29-5_armhf.deb new file mode 100644 index 0000000..797db1c Binary files /dev/null and b/pis/knxd/knxd_0.14.29-5_armhf.deb differ diff --git a/pis/pulseaudio.cfg b/pis/pulseaudio.cfg new file mode 100644 index 0000000..8815267 --- /dev/null +++ b/pis/pulseaudio.cfg @@ -0,0 +1,56 @@ +#!/usr/bin/pulseaudio -nF +# + +### Automatically restore the volume of streams and devices +load-module module-device-restore +load-module module-stream-restore +load-module module-card-restore + +### Disabled: Automatically load driver modules depending on the hardware available +#.ifexists module-udev-detect.so +#load-module module-udev-detect tsched=0 +#.else +### Use the static hardware detection module (for systems that lack udev/hal support) +#load-module module-detect +#.endif + +# this is used instead +load-module module-alsa-card device_id=0 +# Parameters to try +# tsched=true tsched_buffer_size=1048576 tsched_buffer_watermark=262144 + + + +### Load several protocols +.ifexists module-esound-protocol-unix.so +load-module module-esound-protocol-unix +.endif +# here I added anonymous logins from local system +load-module module-native-protocol-unix auth-anonymous=1 + +### Automatically restore the default sink/source when changed by the user +### during runtime +### NOTE: This should be loaded as early as possible so that subsequent modules +### that look up the default sink/source get the right value +load-module module-default-device-restore + +### Automatically move streams to the default sink if the sink they are +### connected to dies, similar for sources +load-module module-rescue-streams + +### Make sure we always have a sink around, even if it is a null sink. +load-module module-always-sink + +### Automatically suspend sinks/sources that become idle for too long +#load-module module-suspend-on-idle + +### Enable positioned event sounds +load-module module-position-event-sounds + + +# enable network streaming from IPs 192.168.178.x +load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1;192.168.178.0/24 auth-anonymous=1 + +# ### Automatically load driver modules for Bluetooth hardware +load-module module-bluetooth-policy +load-module module-bluetooth-discover \ No newline at end of file diff --git a/pis/pulseaudio.service b/pis/pulseaudio.service new file mode 100644 index 0000000..8662c03 --- /dev/null +++ b/pis/pulseaudio.service @@ -0,0 +1,9 @@ +[Unit] +Description=PulseAudio system server + +[Service] +Type=notify +ExecStart=pulseaudio --daemonize=no --system --log-target=journal + +[Install] +WantedBy=multi-user.target diff --git a/pis/raspberry_pi_audio.md b/pis/raspberry_pi_audio.md new file mode 100644 index 0000000..10d8615 --- /dev/null +++ b/pis/raspberry_pi_audio.md @@ -0,0 +1,12 @@ +Switch between line-out and HDMI +-------------------------------- + +amixer cset numid=3 2 # switches to HDMI +amixer cset numid=3 1 # switches to headphone + + +Set up PulseAudio +----------------- + +(also available in playbook TOOD) + diff --git a/pis/start_bt_discoverable.py b/pis/start_bt_discoverable.py new file mode 100644 index 0000000..9c6553f --- /dev/null +++ b/pis/start_bt_discoverable.py @@ -0,0 +1,50 @@ +#!/usr/bin/python3 + +import dbus +import sys + + +def mac_address_to_dbus_path(bus): + manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager") + objects = manager.GetManagedObjects() + result = {} + + for path, interfaces in objects.items(): + for interface, properties in interfaces.items(): + if not interface == 'org.bluez.Adapter1': + continue + result[str(properties['Address'])] = str(path) + return result + + +def start_discovery(bus, dbus_path): + # Make sure the device is powered on + adapter = dbus.Interface(bus.get_object("org.bluez", dbus_path), "org.freedesktop.DBus.Properties") + adapter.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(1)) + + # Get the adapter interface for discovery + dbus.Interface(bus.get_object("org.bluez", dbus_path), "org.bluez.Adapter1").StartDiscovery() + + +def set_alias(bus, dbus_path, new_alias): + adapter = dbus.Interface(bus.get_object("org.bluez", dbus_path), "org.freedesktop.DBus.Properties") + adapter.Set("org.bluez.Adapter1", "Alias", dbus.String(new_alias)) + + +def main(): + mac_address = sys.argv[1] + device_name = sys.argv[2] + + bus = dbus.SystemBus() + mapping = mac_address_to_dbus_path(bus) + try: + dbus_path = mapping[mac_address] + except KeyError: + print("No Device with this MAC address found") + exit(1) + set_alias(bus, dbus_path, device_name) + start_discovery(bus, dbus_path) + + +if __name__ == '__main__': + main() diff --git a/public_keys/martin_laptop.pub b/public_keys/martin_laptop.pub new file mode 100644 index 0000000..4edd8fc --- /dev/null +++ b/public_keys/martin_laptop.pub @@ -0,0 +1,2 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCu66CgHoF+v1z5ydpu0SJzPuAa0eARLLggMAJY4vWcLfLTTlFjwPpO9kjkr4acUL5uLHZkAFqXQZC91io80bIfyBiM1i1yBq290x8sETgoNHrNzvcCQUBAeCxhcogi68F14BbpwBbejDTPKKybpuuAnVPj9YiHVFEDbqjLwoEY+HH7SkCsrK8qTyp9rHzwPGk0xPBwTnCPXqzvUCr/4H+m/5lamVIOW6XYoqnvAp5jP0mbadrmB0PwvK8cfgwPJWQeLJcqwl87mwHjjlrCinkpQbd2D8mR798bGmW/iTZ7GLCkyBNE34qKg24CzE0scWjqyWICXOrTYUXLORDt99/F martin@Laptop +