diff --git a/ansible/roles/ovos_hardware_mark2/README.md b/ansible/roles/ovos_hardware_mark2/README.md new file mode 100644 index 00000000..f7d3b94c --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/README.md @@ -0,0 +1,11 @@ +# ovos_hardware_mark2 + +Setup the VocalFusionDriver kernel module for SJ201 Raspberry Pi HAT running on the Mark II device. + +## License + +Apache 2 + +## Author Information + +Gaëtan Trellu - @goldyfruit diff --git a/ansible/roles/ovos_hardware_mark2/defaults/main.yml b/ansible/roles/ovos_hardware_mark2/defaults/main.yml new file mode 100644 index 00000000..041791bc --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/defaults/main.yml @@ -0,0 +1,4 @@ +--- +ovos_hardware_mark2_eeprom_release: latest +ovos_hardware_mark2_vocalfusion_repo_url: https://github.com/BohdanBuinich/VocalFusionDriver +ovos_hardware_mark2_vocalfusion_branch: next_gen diff --git a/ansible/roles/ovos_hardware_mark2/files/50-alsa-config.lua b/ansible/roles/ovos_hardware_mark2/files/50-alsa-config.lua new file mode 100644 index 00000000..dae708b5 --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/files/50-alsa-config.lua @@ -0,0 +1,155 @@ +alsa_monitor.enabled = true + +alsa_monitor.properties = { + -- Create a JACK device. This is not enabled by default because + -- it requires that the PipeWire JACK replacement libraries are + -- not used by the session manager, in order to be able to + -- connect to the real JACK server. + --["alsa.jack-device"] = false, + + -- Reserve devices via org.freedesktop.ReserveDevice1 on D-Bus + -- Disable if you are running a system-wide instance, which + -- doesn't have access to the D-Bus user session + ["alsa.reserve"] = true, + --["alsa.reserve.priority"] = -20, + --["alsa.reserve.application-name"] = "WirePlumber", + + -- Enables MIDI functionality + ["alsa.midi"] = true, + + -- Enables monitoring of alsa MIDI devices + ["alsa.midi.monitoring"] = true, + + -- MIDI bridge node properties + ["alsa.midi.node-properties"] = { + -- Name set for the node with ALSA MIDI ports + ["node.name"] = "Midi-Bridge", + -- Removes longname/number from MIDI port names + --["api.alsa.disable-longname"] = true, + }, + + -- These properties override node defaults when running in a virtual machine. + -- The rules below still override those. + ["vm.node.defaults"] = { + ["api.alsa.period-size"] = 256, + ["api.alsa.headroom"] = 8192, + }, +} + +alsa_monitor.rules = { + -- An array of matches/actions to evaluate. + -- + -- If you want to disable some devices or nodes, you can apply properties per device as the following example. + -- The name can be found by running pw-cli ls Device, or pw-cli dump Device + --{ + -- matches = { + -- { + -- { "device.name", "matches", "name_of_some_disabled_card" }, + -- }, + -- }, + -- apply_properties = { + -- ["device.disabled"] = true, + -- }, + --} + { + -- Rules for matching a device or node. It is an array of + -- properties that all need to match the regexp. If any of the + -- matches work, the actions are executed for the object. + matches = { + { + -- This matches all cards. + { "device.name", "matches", "alsa_card.*" }, + }, + }, + -- Apply properties on the matched object. + apply_properties = { + -- Use ALSA-Card-Profile devices. They use UCM or the profile + -- configuration to configure the device and mixer settings. + --["api.alsa.use-acp"] = true, + + -- Use UCM instead of profile when available. Can be + -- disabled to skip trying to use the UCM profile. + ["api.alsa.use-ucm"] = true, + + -- Don't use the hardware mixer for volume control. It + -- will only use software volume. The mixer is still used + -- to mute unused paths based on the selected port. + --["api.alsa.soft-mixer"] = false, + + -- Ignore decibel settings of the driver. Can be used to + -- work around buggy drivers that report wrong values. + --["api.alsa.ignore-dB"] = false, + + -- The profile set to use for the device. Usually this is + -- "default.conf" but can be changed with a udev rule or here. + --["device.profile-set"] = "profileset-name", + + -- The default active profile. Is by default set to "Off". + --["device.profile"] = "default profile name", + + -- Automatically select the best profile. This is the + -- highest priority available profile. This is disabled + -- here and instead implemented in the session manager + -- where it can save and load previous preferences. + ["api.acp.auto-profile"] = false, + + -- Automatically switch to the highest priority available port. + -- This is disabled here and implemented in the session manager instead. + ["api.acp.auto-port"] = false, + + -- Other properties can be set here. + --["device.nick"] = "My Device", + }, + }, + { + matches = { + { + -- Matches all sources. + { "node.name", "matches", "alsa_input.*" }, + }, + { + -- Matches all sinks. + { "node.name", "matches", "alsa_output.*" }, + }, + }, + apply_properties = { + --["node.nick"] = "My Node", + --["node.description"] = "My Node Description", + --["priority.driver"] = 100, + --["priority.session"] = 100, + --["node.pause-on-idle"] = false, + --["monitor.channel-volumes"] = false + --["resample.quality"] = 4, + --["resample.disable"] = false, + --["channelmix.normalize"] = false, + --["channelmix.mix-lfe"] = false, + --["channelmix.upmix"] = true, + --["channelmix.upmix-method"] = "psd", -- "none" or "simple" + --["channelmix.lfe-cutoff"] = 150, + --["channelmix.fc-cutoff"] = 12000, + --["channelmix.rear-delay"] = 12.0, + --["channelmix.stereo-widen"] = 0.0, + --["channelmix.hilbert-taps"] = 0, + --["channelmix.disable"] = false, + --["dither.noise"] = 0, + --["dither.method"] = "none", -- "rectangular", "triangular" or "shaped5" + ["audio.channels"] = 2, + ["audio.format"] = "S32LE", + ["audio.rate"] = 48000, + --["audio.allowed-rates"] = "32000,96000", + ["audio.position"] = "FL,FR", + --["api.alsa.period-size"] = 1024, + --["api.alsa.period-num"] = 2, + --["api.alsa.headroom"] = 0, + --["api.alsa.start-delay"] = 0, + --["api.alsa.disable-mmap"] = false, + --["api.alsa.disable-batch"] = false, + --["api.alsa.use-chmap"] = false, + --["api.alsa.multirate"] = true, + --["latency.internal.rate"] = 0 + --["latency.internal.ns"] = 0 + --["clock.name"] = "api.alsa.0" + ["session.suspend-timeout-seconds"] = 0, -- 0 disables suspend + }, + }, +} diff --git a/ansible/roles/ovos_hardware_mark2/handlers/main.yml b/ansible/roles/ovos_hardware_mark2/handlers/main.yml new file mode 100644 index 00000000..95d7254f --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/handlers/main.yml @@ -0,0 +1,13 @@ +--- +- name: Reload Systemd User + become: true + become_user: "{{ ovos_installer_user }}" + ansible.builtin.systemd_service: + daemon_reload: true + scope: user + +- name: Run Depmod + ansible.builtin.command: + cmd: | + depmod -a {{ _ovos_hardware_mark2_kernel_target }} + changed_when: false diff --git a/ansible/roles/ovos_hardware_mark2/meta/main.yml b/ansible/roles/ovos_hardware_mark2/meta/main.yml new file mode 100644 index 00000000..70163989 --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/meta/main.yml @@ -0,0 +1,27 @@ +--- +galaxy_info: + author: Gaëtan trellu (@goldyfruit) + description: Open Voice OS installer Mark II + company: Smart'Gic + standalone: true + + issue_tracker_url: https://github.com/openvoiceos/ovos-installer/issues + + license: Apache-2.0 + + min_ansible_version: "2.12" + + platforms: + - name: Debian + versions: + - bookworm + - bullseye + + galaxy_tags: + - openvoiceos + - ovos + - hivemind + - voiceassistant + - mark2 + +dependencies: [] diff --git a/ansible/roles/ovos_hardware_mark2/tasks/firmware.yml b/ansible/roles/ovos_hardware_mark2/tasks/firmware.yml new file mode 100644 index 00000000..a63e6217 --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/tasks/firmware.yml @@ -0,0 +1,80 @@ +--- +# This block is only meaningful until kernel 6.6.22 becomes +# the default Raspberry Pi stock kernel. +# The current stock kernel version is 6.6.20 which does not work +# with the VocalFusionDriver. +# rpi-firmware commit f17defccf231912cfe96c9d3a779b4e2d006abd6 (6.6.22) +- name: Block kernel upgrade + when: ansible_kernel is version("6.6.22", "<") + block: + - name: Kernel headers packages requirement + ansible.builtin.apt: + name: + - bc + - bison + - flex + - libssl-dev + install_recommends: false + update_cache: true + + - name: Update kernel and firmware + ansible.builtin.shell: + cmd: | + set -o pipefail + echo y | rpi-update f17defccf231912cfe96c9d3a779b4e2d006abd6 + executable: /bin/bash + environment: + SKIP_BACKUP: "1" + UPDATE_SELF: "1" + register: _firmware_status + changed_when: no + + - name: Retrieve rpi-source Python script + ansible.builtin.get_url: + url: "{{ _ovos_hardware_mark2_rpi_source_url }}" + dest: /usr/local/bin/rpi-source + owner: root + group: root + mode: "0755" + + - name: Build kernel headers + ansible.builtin.shell: + cmd: | + set -o pipefail + rpi-source -q --tag-update + echo -e -n "\r" | sudo rpi-source -d /usr/src/ --delete + ln -sf /usr/src/linux {{ _ovos_hardware_mark2_lib_modules_path }}/build + executable: /bin/bash + changed_when: no + + - name: Set ovos_installer_reboot fact (firmware) + vars: + _firmware_reboot: "{{ _firmware_status.stdout | regex_search('reboot') }}" + ansible.builtin.set_fact: + ovos_installer_reboot: true + when: _firmware_status is defined and _firmware_reboot | length > 0 + +- name: Install kernel headers + ansible.builtin.apt: + name: raspberrypi-kernel-headers + install_recommends: false + update_cache: true + +- name: Set EEPROM release + ansible.builtin.lineinfile: + path: /etc/default/rpi-eeprom-update + regexp: "^FIRMWARE_RELEASE_STATUS=" + line: 'FIRMWARE_RELEASE_STATUS="{{ ovos_hardware_mark2_eeprom_release }}"' + +- name: Update EEPROM + ansible.builtin.command: + cmd: rpi-eeprom-update -a + register: _eeprom_status + changed_when: no + +- name: Set ovos_installer_reboot fact (EEPROM) + vars: + _eeprom_reboot: "{{ _eeprom_status.stdout | regex_search('reboot') }}" + ansible.builtin.set_fact: + ovos_installer_reboot: true + when: _eeprom_status is defined and _eeprom_reboot | length > 0 diff --git a/ansible/roles/ovos_hardware_mark2/tasks/main.yml b/ansible/roles/ovos_hardware_mark2/tasks/main.yml new file mode 100644 index 00000000..acf317a0 --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/tasks/main.yml @@ -0,0 +1,12 @@ +--- +- name: Include prepare.yml + ansible.builtin.import_tasks: prepare.yml + +- name: Include firmware.yml + ansible.builtin.import_tasks: firmware.yml + +- name: Include vocalfusion.yml + ansible.builtin.import_tasks: vocalfusion.yml + +- name: Include wireplumber.yml + ansible.builtin.import_tasks: wireplumber.yml diff --git a/ansible/roles/ovos_hardware_mark2/tasks/prepare.yml b/ansible/roles/ovos_hardware_mark2/tasks/prepare.yml new file mode 100644 index 00000000..5b422eaa --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/tasks/prepare.yml @@ -0,0 +1,12 @@ +--- +- name: Create directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ ovos_installer_user }}" + group: "{{ ovos_installer_user }}" + mode: "0755" + loop: + - /opt/sj201 + - "{{ ovos_installer_user_home }}/.config/systemd/user" + - "{{ ovos_installer_user_home }}/.config/wireplumber/main.lua.d" diff --git a/ansible/roles/ovos_hardware_mark2/tasks/vocalfusion.yml b/ansible/roles/ovos_hardware_mark2/tasks/vocalfusion.yml new file mode 100644 index 00000000..3e78ce6d --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/tasks/vocalfusion.yml @@ -0,0 +1,117 @@ +--- +- name: Clone VocalFusionDriver Git repository + ansible.builtin.git: + repo: "{{ ovos_hardware_mark2_vocalfusion_repo_url }}" + dest: "{{ _ovos_hardware_mark2_vocalfusion_src_path }}" + version: "{{ ovos_hardware_mark2_vocalfusion_branch }}" + +- name: Copy DTBO files to /boot/overlays + vars: + _is_rpi5: "{{ '-pi5' if 'Raspberry Pi 5' in ovos_installer_raspberrypi else '' }}" + _dtbo_file: "{{ item }}{{ _is_rpi5 }}.dtbo" + ansible.builtin.copy: + src: "{{ _ovos_hardware_mark2_vocalfusion_src_path }}/{{ _dtbo_file }}" + dest: "/boot/overlays/{{ _dtbo_file }}" + owner: root + group: root + mode: "0755" + remote_src: true + loop: + - sj201 + - sj201-buttons-overlay + - sj201-rev10-pwm-fan-overlay + +- name: Manage sj201, buttons and PWM overlays + vars: + _is_rpi5: "{{ '-pi5' if 'Raspberry Pi 5' in ovos_installer_raspberrypi else '' }}" + ansible.builtin.lineinfile: + path: /boot/firmware/config.txt + regexp: "^{{ item }}=" + line: "{{ item }}{{ _is_rpi5 }}" + loop: + - dtoverlay=sj201 + - dtoverlay=sj201-buttons-overlay + - dtoverlay=sj201-rev10-pwm-fan-overlay + +- name: Build vocalfusion-soundcard.ko kernel module + ansible.builtin.shell: + cmd: | + sudo make -j {{ ansible_processor_count }} KDIR={{ _ovos_hardware_mark2_lib_modules_path }}/build all + executable: /bin/bash + chdir: "{{ _ovos_hardware_mark2_vocalfusion_src_path }}/driver" + changed_when: false + +- name: Copy vocalfusion-soundcard.ko to {{ _ovos_hardware_mark2_lib_modules_path }} + ansible.builtin.copy: + src: "{{ _ovos_hardware_mark2_vocalfusion_src_path }}/driver/vocalfusion-soundcard.ko" + dest: "{{ _ovos_hardware_mark2_lib_modules_path }}/vocalfusion-soundcard.ko" + owner: root + group: root + mode: "0644" + notify: Run Depmod + +- name: Create /etc/modules-load.d/vocalfusion.conf file + ansible.builtin.copy: + content: | + vocalfusion-soundcard + dest: /etc/modules-load.d/vocalfusion.conf + owner: root + group: root + mode: "0644" + +- name: Create {{ ovos_installer_user_home }}/.venvs/sj201 Python + ansible.builtin.pip: + name: + - Adafruit-Blinka + - smbus2 + - RPi.GPIO + - gpiod + virtualenv: "{{ ovos_installer_user_home }}/.venvs/sj201" + virtualenv_command: "{{ ansible_python.executable }} -m venv" + +- name: Download SJ201 firmware and scripts + ansible.builtin.get_url: + url: "{{ item.url }}" + dest: "/opt/sj201/{{ item.dest }}" + owner: root + group: root + mode: "0755" + loop: + - { + "url": "https://raw.githubusercontent.com/OpenVoiceOS/ovos-buildroot/0e464466194f58553af11c34f7435dba76ec70a3/buildroot-external/package/vocalfusion/xvf3510-flash", + "dest": "xvf3510-flash", + } + - { + "url": "https://raw.githubusercontent.com/OpenVoiceOS/ovos-buildroot/c67d7f0b7f2a3eff5faab96d6adf7495e9b48b93/buildroot-external/package/vocalfusion/app_xvf3510_int_spi_boot_v4_2_0.bin", + "dest": "app_xvf3510_int_spi_boot_v4_2_0.bin", + } + - { + "url": "https://raw.githubusercontent.com/MycroftAI/mark-ii-hardware-testing/main/utils/init_tas5806.py", + "dest": "init_tas5806", + } + +- name: Copy SJ201 systemd unit file + ansible.builtin.template: + src: sj201.service.j2 + dest: "{{ ovos_installer_user_home }}/.config/systemd/user/sj201.service" + owner: "{{ ovos_installer_user }}" + group: "{{ ovos_installer_user }}" + mode: "0644" + notify: Reload Systemd User + +- name: Flush handlers vocalfusion + ansible.builtin.meta: flush_handlers + +- name: Enable SJ201 systemd unit + become: true + become_user: "{{ ovos_installer_user }}" + ansible.builtin.systemd_service: + name: sj201.service + enabled: true + force: true + scope: user + +- name: Delete /usr/src/vocalfusion once compiled + ansible.builtin.file: + path: /usr/src/vocalfusion + state: absent diff --git a/ansible/roles/ovos_hardware_mark2/tasks/wireplumber.yml b/ansible/roles/ovos_hardware_mark2/tasks/wireplumber.yml new file mode 100644 index 00000000..8248d297 --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/tasks/wireplumber.yml @@ -0,0 +1,8 @@ +--- +- name: Configure ALSA for WirePlumber + ansible.builtin.copy: + src: 50-alsa-config.lua + dest: "{{ ovos_installer_user_home }}/.config/wireplumber/main.lua.d" + owner: "{{ ovos_installer_user }}" + group: "{{ ovos_installer_user }}" + mode: "0644" diff --git a/ansible/roles/ovos_hardware_mark2/templates/sj201.service.j2 b/ansible/roles/ovos_hardware_mark2/templates/sj201.service.j2 new file mode 100644 index 00000000..a400f3f2 --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/templates/sj201.service.j2 @@ -0,0 +1,16 @@ +[Unit] +Documentation=https://github.com/MycroftAI/mark-ii-hardware-testing/blob/main/README.md +Description=SJ201 microphone initialization +After=network-online.target + +[Service] +Type=oneshot +WorkingDirectory=%h/.venvs/sj201 +ExecStart=%h/.venvs/sj201/bin/python /opt/sj201/xvf3510-flash --direct /opt/sj201/app_xvf3510_int_spi_boot_v4_2_0.bin --verbose +ExecStartPost=%h/.venvs/sj201/bin/python /opt/sj201/init_tas5806 +Restart=on-failure +RestartSec=5s +RemainAfterExit=yes + +[Install] +WantedBy=default.target diff --git a/ansible/roles/ovos_hardware_mark2/vars/main.yml b/ansible/roles/ovos_hardware_mark2/vars/main.yml new file mode 100644 index 00000000..5d365d79 --- /dev/null +++ b/ansible/roles/ovos_hardware_mark2/vars/main.yml @@ -0,0 +1,6 @@ +--- +# Until https://github.com/RPi-Distro/rpi-source/pull/29 PR is merged +_ovos_hardware_mark2_rpi_source_url: https://raw.githubusercontent.com/jgartrel/rpi-source/master/rpi-source +_ovos_hardware_mark2_kernel_target: "{{ '6.6.22-v8+' if 'Raspberry Pi 4' in ovos_installer_raspberrypi else '6.6.22-v8-16k+' }}" +_ovos_hardware_mark2_lib_modules_path: "/lib/modules/{{ _ovos_hardware_mark2_kernel_target }}" +_ovos_hardware_mark2_vocalfusion_src_path: /usr/src/vocalfusion diff --git a/ansible/roles/ovos_installer/README.md b/ansible/roles/ovos_installer/README.md index 225dd44b..67f07a51 100644 --- a/ansible/roles/ovos_installer/README.md +++ b/ansible/roles/ovos_installer/README.md @@ -1,38 +1,23 @@ -Role Name -========= +# ovos_installer -A brief description of the role goes here. +Install Open Voice OS as a Python virtual environment or Docker containers. -Requirements ------------- +## Dependencies -Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. +```yaml +collections: + - name: ansible.posix + version: 1.5.4 + - name: community.docker + version: 3.7.0 + - name: community.general + version: 8.3.0 +``` -Role Variables --------------- +## License -A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. +Apache 2 -Dependencies ------------- +## Author Information -A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. - -Example Playbook ----------------- - -Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: - - - hosts: servers - roles: - - { role: username.rolename, x: 42 } - -License -------- - -BSD - -Author Information ------------------- - -An optional section for the role authors to include contact information, or a website (HTML is not allowed). +Gaëtan Trellu - @goldyfruit diff --git a/ansible/roles/ovos_installer/tasks/docker/composer.yml b/ansible/roles/ovos_installer/tasks/docker/composer.yml index 681d38a5..df273db3 100644 --- a/ansible/roles/ovos_installer/tasks/docker/composer.yml +++ b/ansible/roles/ovos_installer/tasks/docker/composer.yml @@ -58,11 +58,11 @@ } - { "file": "docker-compose.gui.yml", - "state": "{{ 'true' if (ansible_memtotal_mb >= 2048 and ovos_installer_profile != 'satellite' and ovos_installer_feature_gui | bool) else 'false' }}", + "state": "{{ 'true' if (ansible_memtotal_mb >= 1740 and ovos_installer_profile != 'satellite' and ovos_installer_feature_gui | bool) else 'false' }}", } - { "file": "docker-compose.raspberrypi.gui.yml", - "state": "{{ 'true' if (ansible_memtotal_mb >= 2048 and ovos_installer_profile != 'satellite' and 'Raspberry Pi 4' in ovos_installer_raspberrypi and ovos_installer_feature_gui | bool) else 'false' }}", + "state": "{{ 'true' if (ansible_memtotal_mb >= 1740 and ovos_installer_profile != 'satellite' and 'Raspberry Pi 4' in ovos_installer_raspberrypi and ovos_installer_feature_gui | bool) else 'false' }}", } - { "file": "docker-compose.satellite.yml", diff --git a/ansible/roles/ovos_installer/tasks/tuning/io.yml b/ansible/roles/ovos_installer/tasks/tuning/io.yml index d597a634..1cd34c88 100644 --- a/ansible/roles/ovos_installer/tasks/tuning/io.yml +++ b/ansible/roles/ovos_installer/tasks/tuning/io.yml @@ -9,7 +9,7 @@ - name: Manage I2C, SPI and I2S buses ansible.builtin.lineinfile: - path: /boot/config.txt + path: /boot/firmware/config.txt regexp: "^{{ item.key }}=" line: "{{ item.key }}={{ item.value }}" loop: diff --git a/ansible/roles/ovos_installer/tasks/virtualenv/packages.yml b/ansible/roles/ovos_installer/tasks/virtualenv/packages.yml index be197133..97a7eccc 100644 --- a/ansible/roles/ovos_installer/tasks/virtualenv/packages.yml +++ b/ansible/roles/ovos_installer/tasks/virtualenv/packages.yml @@ -37,8 +37,6 @@ install_recommends: false state: "{{ ovos_installer_uninstall }}" when: ansible_os_family == "Debian" - tags: - - always - name: Handle virtualenv package requirements (ovos/hivemind) ansible.builtin.dnf: @@ -59,8 +57,6 @@ - portaudio-devel state: "{{ ovos_installer_uninstall }}" when: ansible_os_family == "RedHat" - tags: - - always - name: Handle virtualenv package requirements (ovos/hivemind) community.general.zypper: @@ -79,8 +75,6 @@ - portaudio-devel state: "{{ ovos_installer_uninstall }}" when: ansible_os_family == "Suse" - tags: - - always # Package on Archlinux OS family will not be uninstalled as it might # break the system... diff --git a/ansible/roles/ovos_installer/tasks/virtualenv/venv.yml b/ansible/roles/ovos_installer/tasks/virtualenv/venv.yml index b5a0e559..32f76e32 100644 --- a/ansible/roles/ovos_installer/tasks/virtualenv/venv.yml +++ b/ansible/roles/ovos_installer/tasks/virtualenv/venv.yml @@ -111,6 +111,20 @@ loop: - { "line": "VIRTUAL_ENV={{ _path }}", "regexp": "^VIRTUAL_ENV" } - { "line": "PATH=$PATH:{{ _path }}", "regexp": "^PATH" } + when: ovos_installer_i2c_devices | from_json | length < 1 + tags: + - always + +- name: Add auto Python virtualenv loading on auto-detected devices + ansible.builtin.lineinfile: + path: "{{ ovos_installer_user_home }}/.bashrc" + line: "source {{ ovos_installer_user_home }}/.venvs/ovos/bin/activate" + regexp: "^source {{ ovos_installer_user_home }}/.venvs/ovos/bin/activate" + owner: "{{ ovos_installer_user }}" + group: "{{ ovos_installer_user }}" + mode: "0644" + state: "{{ ovos_installer_uninstall }}" + when: ovos_installer_i2c_devices | from_json | length >= 1 tags: - always diff --git a/ansible/roles/ovos_installer/templates/mycroft.conf.j2 b/ansible/roles/ovos_installer/templates/mycroft.conf.j2 index 6ce0a641..d2d4380d 100644 --- a/ansible/roles/ovos_installer/templates/mycroft.conf.j2 +++ b/ansible/roles/ovos_installer/templates/mycroft.conf.j2 @@ -37,12 +37,27 @@ "websocket": { "max_msg_size": {{ 100 if ovos_installer_feature_gui | bool and ovos_installer_method == "containers" else 25 }} }, + "PHAL": { +{% if 'tas5806' in ovos_installer_i2c_devices %} + "ovos-PHAL-plugin-hotkeys": { + "key_down": { + "mycroft.mic.listen": 143, + "mycroft.mic.mute": 113, + "mycroft.volume.increase": 115, + "mycroft.volume.decrease": 114 + }, + "key_up": { + "mycroft.mic.unmute": 113 + } + } +{% endif %} + }, "tts": { "ovos-tts-plugin-server": { {% if ovos_installer_locale == "de-de" %} - "voice": "thorsten-high" + "voice": "thorsten-low" {% elif ovos_installer_locale == "en-us" %} - "voice": "ryan-high" + "voice": "ryan-low" {% elif ovos_installer_locale == "es-es" %} "voice": "davefx-medium" {% elif ovos_installer_locale == "fr-fr" %} diff --git a/ansible/roles/ovos_installer/templates/telemetry.json.j2 b/ansible/roles/ovos_installer/templates/telemetry.json.j2 index 041c8c55..d69915d4 100644 --- a/ansible/roles/ovos_installer/templates/telemetry.json.j2 +++ b/ansible/roles/ovos_installer/templates/telemetry.json.j2 @@ -18,5 +18,6 @@ "gui_feature": {{ ovos_installer_feature_gui }}, "cpu_capable": {{ ovos_installer_cpu_is_capable }}, "tuning_enabled": {{ true if ovos_installer_tuning == 'yes' else false }}, - "country": "{{ _isp_data.json.country | lower }}" + "country": "{{ _isp_data.json.country | lower }}", + "hardware": "{{ 'n/a' if ovos_installer_i2c_devices | from_json | length < 1 else ovos_installer_i2c_devices | from_json | first }}" } diff --git a/ansible/roles/ovos_installer/templates/virtualenv/core-requirements.txt.j2 b/ansible/roles/ovos_installer/templates/virtualenv/core-requirements.txt.j2 index b2adb386..39e803bc 100644 --- a/ansible/roles/ovos_installer/templates/virtualenv/core-requirements.txt.j2 +++ b/ansible/roles/ovos_installer/templates/virtualenv/core-requirements.txt.j2 @@ -37,6 +37,9 @@ git+https://github.com/OpenVoiceOS/ovos-cli-client.git git+https://github.com/OpenVoiceOS/ovos-docs-viewer.git git+https://github.com/OpenVoiceOS/ovos-ocp-news-plugin.git git+https://github.com/OpenVoiceOS/ovos-ocp-youtube-plugin.git +{% if 'tas5806' in ovos_installer_i2c_devices %} +git+https://github.com/OpenVoiceOS/ovos-PHAL-plugin-hotkeys.git +{% endif %} git+https://github.com/OpenVoiceOS/ovos-stt-plugin-chromium.git git+https://github.com/OpenVoiceOS/ovos-tts-plugin-polly.git git+https://github.com/OpenVoiceOS/ovos-utterance-corrections-plugin.git diff --git a/ansible/site.yml b/ansible/site.yml index 89a1ddbf..e8e7a5bf 100644 --- a/ansible/site.yml +++ b/ansible/site.yml @@ -6,6 +6,9 @@ user: "{{ ovos_installer_user }}" become: true + vars: + ovos_installer_reboot: false + pre_tasks: - name: Gather reduced subset of facts ansible.builtin.setup: @@ -16,4 +19,21 @@ - always roles: + - role: ovos_hardware_mark2 + when: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('11', '>=') + - "'Raspberry Pi 4' in ovos_installer_raspberrypi or 'Raspberry Pi 5' in ovos_installer_raspberrypi" + - "'tas5806' in ovos_installer_i2c_devices" + - ansible_architecture == "aarch64" - role: ovos_installer + + tasks: + - name: Checking for reboot requirement + ansible.builtin.file: + path: "{{ ovos_installer_reboot_file_path }}" + state: touch + owner: root + group: root + mode: "0644" + when: ovos_installer_reboot | bool diff --git a/setup.sh b/setup.sh index 0cf9c3bf..7206cb7a 100644 --- a/setup.sh +++ b/setup.sh @@ -44,6 +44,7 @@ create_python_venv install_ansible download_yq detect_scenario +i2c_scan trap "" ERR set +eE @@ -102,6 +103,8 @@ unbuffer ansible-playbook -i 127.0.0.1, ansible/site.yml \ -e "ovos_installer_display_server=${DISPLAY_SERVER}" \ -e "ovos_installer_telemetry=${SHARE_TELEMETRY}" \ -e "ovos_installer_locale=${LOCALE:-en-us}" \ + -e "ovos_installer_i2c_devices=$(jq -c -n '$ARGS.positional' --args "${DETECTED_DEVICES[@]}")" \ + -e "ovos_installer_reboot_file_path=${REBOOT_FILE_PATH}" \ "${ansible_tags[@]}" "${ansible_debug[@]}" | tee -a "$LOG_FILE" # Retrieve the ansible-playbook status code before tee command and check for success or failure @@ -110,6 +113,10 @@ if [ "${PIPESTATUS[0]}" -eq 0 ]; then if [ "$SCENARIO_FOUND" == "false" ]; then # shellcheck source=tui/finish.sh source tui/finish.sh + if [ -f "$REBOOT_FILE_PATH" ]; then + rm -f "$REBOOT_FILE_PATH" + shutdown -r now + fi fi else rm -rf "$VENV_PATH" diff --git a/tests/bats/i2c.bats b/tests/bats/i2c.bats new file mode 100644 index 00000000..170d7c19 --- /dev/null +++ b/tests/bats/i2c.bats @@ -0,0 +1,33 @@ +#!/usr/bin/env bats + +function setup() { + load "$HOME/shell-testing/test_helper/bats-support/load" + load "$HOME/shell-testing/test_helper/bats-assert/load" + load ../../utils/constants.sh + load ../../utils/common.sh + LOG_FILE=/tmp/ovos-installer.log +} + +@test "function_install_i2c_get_exists" { + function i2cdetect() { + echo "2f" + } + export -f i2cdetect + run i2c_get "2f" + assert_success + unset i2cdetect +} + +@test "function_install_i2c_get_non_exists" { + function i2cdetect() { + echo "" + } + export -f i2cdetect + run i2c_get "2f" + assert_failure + unset i2cdetect +} + +function teardown() { + rm -f "$LOG_FILE" +} diff --git a/tui/detection.sh b/tui/detection.sh index d26427cf..357ab11f 100644 --- a/tui/detection.sh +++ b/tui/detection.sh @@ -1,5 +1,16 @@ #!/bin/env bash +HARDWARE_DETECTED="N/A" + +for device in "${DETECTED_DEVICES[@]}"; do + case ${device} in + tas5806) + HARDWARE_DETECTED="Mycroft Mark II" + ;; + esac +done +export HARDWARE_DETECTED + # shellcheck source=locales/en-us/detection.sh source "tui/locales/$LOCALE/detection.sh" diff --git a/tui/locales/de-de/detection.sh b/tui/locales/de-de/detection.sh index 938d9746..f4d24ce6 100644 --- a/tui/locales/de-de/detection.sh +++ b/tui/locales/de-de/detection.sh @@ -8,6 +8,7 @@ Automatisch erkannte Systemeigenschaften: - RPi: $RASPBERRYPI_MODEL - Python: $(echo "$PYTHON" | awk '{ print $NF }') - AVX/SIMD: $CPU_IS_CAPABLE + - Hardware: $HARDWARE_DETECTED - Venv: $VENV_PATH - Sound: $SOUND_SERVER - Display: ${DISPLAY_SERVER^} diff --git a/tui/locales/en-us/detection.sh b/tui/locales/en-us/detection.sh index 9c9d0827..b167e720 100644 --- a/tui/locales/en-us/detection.sh +++ b/tui/locales/en-us/detection.sh @@ -8,6 +8,7 @@ Please find the detected information: - RPi: $RASPBERRYPI_MODEL - Python: $(echo "$PYTHON" | awk '{ print $NF }') - AVX/SIMD: $CPU_IS_CAPABLE + - Hardware: $HARDWARE_DETECTED - Venv: $VENV_PATH - Sound: $SOUND_SERVER - Display: ${DISPLAY_SERVER^} diff --git a/tui/locales/es-es/detection.sh b/tui/locales/es-es/detection.sh index 5a9aa695..6d5d2708 100644 --- a/tui/locales/es-es/detection.sh +++ b/tui/locales/es-es/detection.sh @@ -8,6 +8,7 @@ Propiedades del sistema reconocidas automáticamente: - RPi: $RASPBERRYPI_MODEL - Python: $(echo "$PYTHON" | awk '{ print $NF }') - AVX/SIMD: $CPU_IS_CAPABLE + - Hardware: $HARDWARE_DETECTED - Venv: $VENV_PATH - Sound: $SOUND_SERVER - Display: ${DISPLAY_SERVER^} diff --git a/tui/locales/fr-fr/detection.sh b/tui/locales/fr-fr/detection.sh index 6406cb22..74b7d9d2 100644 --- a/tui/locales/fr-fr/detection.sh +++ b/tui/locales/fr-fr/detection.sh @@ -8,6 +8,7 @@ Veuillez trouver ci-dessous les information détectées: - RPi: $RASPBERRYPI_MODEL - Python: $(echo "$PYTHON" | awk '{ print $NF }') - AVX/SIMD: $CPU_IS_CAPABLE + - Matériel: $HARDWARE_DETECTED - Environnement virtuel: $VENV_PATH - Serveur de son: $SOUND_SERVER - Serveur graphique: ${DISPLAY_SERVER^} diff --git a/tui/locales/it-it/detection.sh b/tui/locales/it-it/detection.sh index 608270db..fedcd312 100644 --- a/tui/locales/it-it/detection.sh +++ b/tui/locales/it-it/detection.sh @@ -8,6 +8,7 @@ Proprietà del sistema riconosciute automaticamente: - RPi: $RASPBERRYPI_MODEL - Python: $(echo "$PYTHON" | awk '{ print $NF }') - AVX/SIMD: $CPU_IS_CAPABLE + - Hardware: $HARDWARE_DETECTED - Venv: $VENV_PATH - Sound: $SOUND_SERVER - Display: ${DISPLAY_SERVER^} diff --git a/tui/locales/nl-nl/detection.sh b/tui/locales/nl-nl/detection.sh index 9ca34635..fbb73b7d 100644 --- a/tui/locales/nl-nl/detection.sh +++ b/tui/locales/nl-nl/detection.sh @@ -3,14 +3,15 @@ CONTENT=" Automatisch herkende systeemeigenschappen: - - OS: ${DISTRO_NAME^} $DISTRO_VERSION - - Kernel: $KERNEL - - RPi: $RASPBERRYPI_MODEL - - Python: $(echo "$PYTHON" | awk '{ print $NF }') - - AVX/SIMD: $CPU_IS_CAPABLE - - Venv: $VENV_PATH - - Geluid: $SOUND_SERVER - - Beeldscherm: ${DISPLAY_SERVER^} + - OS: ${DISTRO_NAME^} $DISTRO_VERSION + - Kernel: $KERNEL + - RPi: $RASPBERRYPI_MODEL + - Python: $(echo "$PYTHON" | awk '{ print $NF }') + - AVX/SIMD: $CPU_IS_CAPABLE + - sprzęt komputerowy: $HARDWARE_DETECTED + - Venv: $VENV_PATH + - Geluid: $SOUND_SERVER + - Beeldscherm: ${DISPLAY_SERVER^} " TITLE="Open Voice OS Installatie - Systeemeigenschappen" diff --git a/tui/locales/pt-pt/detection.sh b/tui/locales/pt-pt/detection.sh index 7e5b42d8..d39a8f75 100644 --- a/tui/locales/pt-pt/detection.sh +++ b/tui/locales/pt-pt/detection.sh @@ -8,6 +8,7 @@ Propriedades do sistema reconhecidas automaticamente: - RPi: $RASPBERRYPI_MODEL - Python: $(echo "$PYTHON" | awk '{ print $NF }') - AVX/SIMD: $CPU_IS_CAPABLE + - Hardware: $HARDWARE_DETECTED - Venv: $VENV_PATH - Som: $SOUND_SERVER - Ecrã: ${DISPLAY_SERVER^} diff --git a/utils/common.sh b/utils/common.sh index 3c79e330..4f7a114f 100644 --- a/utils/common.sh +++ b/utils/common.sh @@ -192,27 +192,34 @@ function get_os_information() { function required_packages() { echo -ne "➤ Validating installer package requirements... " PYTHON_VERSION="$MAX_PYTHON_VERSION" + + # Add extra packages if a Raspberry Pi board is detected + declare extra_packages + if [ "$RASPBERRYPI_MODEL" != "N/A" ]; then + extra_packages+=("i2c-tools") + fi + case "$DISTRO_NAME" in debian | ubuntu | raspbian | linuxmint) [ "$DISTRO_VERSION_ID" == "11" ] && export PYTHON_VERSION="3" apt-get update &>>"$LOG_FILE" - apt-get install --no-install-recommends -y "python${PYTHON_VERSION}" "python${PYTHON_VERSION}-dev" python3-pip "python${PYTHON_VERSION}-venv" whiptail expect jq &>>"$LOG_FILE" + apt-get install --no-install-recommends -y "python${PYTHON_VERSION}" "python${PYTHON_VERSION}-dev" python3-pip "python${PYTHON_VERSION}-venv" whiptail expect jq "${extra_packages[@]}" &>>"$LOG_FILE" ;; fedora) export PYTHON_VERSION - dnf install -y python3.11 python3.11-devel python3-pip python3-virtualenv newt expect jq &>>"$LOG_FILE" + dnf install -y python3.11 python3.11-devel python3-pip python3-virtualenv newt expect jq "${extra_packages[@]}" &>>"$LOG_FILE" ;; rocky | centos) export PYTHON_VERSION - dnf install -y python3.11 python3.11-devel python3-pip newt expect jq &>>"$LOG_FILE" + dnf install -y python3.11 python3.11-devel python3-pip newt expect jq "${extra_packages[@]}" &>>"$LOG_FILE" ;; opensuse-tumbleweed | opensuse-leap) export PYTHON_VERSION - zypper install -y python311 python311-devel python3-pip python3-rpm newt expect jq &>>"$LOG_FILE" + zypper install -y python311 python311-devel python3-pip python3-rpm newt expect jq "${extra_packages[@]}" &>>"$LOG_FILE" ;; arch | manjaro | endeavouros) export PYTHON_VERSION - pacman -Sy --noconfirm python python-pip python-virtualenv libnewt expect jq &>>"$LOG_FILE" + pacman -Sy --noconfirm python python-pip python-virtualenv libnewt expect jq "${extra_packages[@]}" &>>"$LOG_FILE" ;; *) echo -e "[$fail_format]" @@ -337,3 +344,38 @@ function ver() { # shellcheck disable=SC2046 printf "%03d" $(echo "$1" | tr '.' ' ') } + +# Check if an hexacidemal address exists on the I2C bus +# This function takes an argument like "2f", this will be converted +# to "0x2f". +function i2c_get() { + if i2cdetect -y -a "$I2C_BUS" "0x$1" "0x$1" 2>>"$LOG_FILE" | grep -q "$1"; then + return 0 + fi + return 1 +} + +# Scan the I2C bus to find any supported devices +# This function will only run if a Raspberry Pi board is detected. +function i2c_scan() { + if [ "$RASPBERRYPI_MODEL" != "N/A" ]; then + echo -ne "➤ Scan I2C bus for hardware auto-detection..." + + # Load I2C requirements if not ready, nothing persistent here as + # it will be handled later by the Ansible playbook. + if ! dtparam -l | grep -q i2c_arm=on; then + dtparam -v i2c_arm=on &>>"$LOG_FILE" + fi + if ! lsmod | grep -q i2c-dev; then + modprobe -v i2c-dev &>>"$LOG_FILE" + fi + + for device in "${!SUPPORTED_DEVICES[@]}"; do + address="${SUPPORTED_DEVICES[$device]}" + if i2c_get "$address"; then + DETECTED_DEVICES+=("$device") + fi + done + echo -e "[$done_format]" + fi +} diff --git a/utils/constants.sh b/utils/constants.sh index 49cda5ef..fa4beb3b 100644 --- a/utils/constants.sh +++ b/utils/constants.sh @@ -1,6 +1,9 @@ #!/bin/env bash +declare -a DETECTED_DEVICES +export DETECTED_DEVICES export DT_FILE=/sys/firmware/devicetree/base/model +export I2C_BUS="1" export INSTALLER_VENV_NAME="ovos-installer" export LOG_FILE=/var/log/ovos-installer.log export MAX_PYTHON_VERSION="3.11" @@ -31,14 +34,19 @@ export NEWT_COLORS=" " export OS_RELEASE=/etc/os-release export PULSE_SOCKET_WSL2=/mnt/wslg/PulseServer -declare -a SCENARIO_ALLOWED_OPTIONS=(features channel share_telemetry profile method uninstall rapsberry_pi_tuning hivemind) +export REBOOT_FILE_PATH=/tmp/ovos.reboot +declare -ra SCENARIO_ALLOWED_OPTIONS=(features channel share_telemetry profile method uninstall rapsberry_pi_tuning hivemind) export SCENARIO_ALLOWED_OPTIONS -declare -a SCENARIO_ALLOWED_FEATURES=(skills gui) +declare -ra SCENARIO_ALLOWED_FEATURES=(skills gui) export SCENARIO_ALLOWED_FEATURES -declare -a SCENARIO_ALLOWED_HIVEMIND_OPTIONS=(host port key password) +declare -ra SCENARIO_ALLOWED_HIVEMIND_OPTIONS=(host port key password) export SCENARIO_ALLOWED_HIVEMIND_OPTIONS export SCENARIO_NAME="scenario.yaml" export SCENARIO_PATH="" +declare -rA SUPPORTED_DEVICES=( + ["tas5806"]="2f" #https://www.ti.com/product/TAS5806MD +) +export SUPPORTED_DEVICES export TUI_WINDOW_HEIGHT="35" export TUI_WINDOW_WIDTH="80" export USER_ID="$EUID"