Hi,
I am working on a reliable connection of my Zigbee Dongle (Conbee II) to Home Assistant Core which is running as Docker Container. I am using usbip
to bring the Zigbee Dongle from a Raspberry Pi to my Docker Server.
On the Raspberry Pi I have already a reboot-safe and hotplug-stable solution. Here the implementation steps as Ansible Playbook:
---
# root@dashboard:/etc/systemd/system# lsusb -d 1cf1:0030 -v
# Bus 001 Device 011: ID 1cf1:0030 Dresden Elektronik
# Device Descriptor:
# bLength 18
# bDescriptorType 1
# bcdUSB 2.01
# bDeviceClass 2 Communications
# bDeviceSubClass 0
# bDeviceProtocol 0
# bMaxPacketSize0 64
# idVendor 0x1cf1 Dresden Elektronik
# idProduct 0x0030
# bcdDevice 1.00
# iManufacturer 1 dresden elektronik ingenieurtechnik GmbH
# iProduct 2 ConBee II
# iSerial 3 DE2154525
# bNumConfigurations 1
#
# -> Zigbee Host
#
- name: Setup usbip on Zigbee Stick Host
hosts:
- dashboard
vars:
usbvendorid: "1cf1"
usbproductid: "0030"
tasks:
- name: Install usbip
package:
name: usbip
state: present
- name: Load kernel modules at boot
copy:
dest: /etc/modules-load.d/usbipd.conf
owner: root
group: root
mode: u=rw,go=r
content: |
# load kernel modules needed for usbip daemon
# this file is managed by ansible
usbip_host
- name: Load kernel modules now
modprobe:
name: usbip_host
state: present
- name: "Create vendor:product bind script for usbip"
copy:
dest: /usr/sbin/usbip-bind-vendorproduct
owner: root
group: root
mode: u=rwx,go=r
content: |
#!/bin/bash
VENDORPRODUCT=$(echo "$1" | sed 's/-/:/g')
MODE=$2
BUSID=$(usbip list -l | grep -oP "busid\s+([0-9\-\.]+)\s+\(${VENDORPRODUCT}\)" | head -n 1 | awk '{print $2}')
USBIPACTIVE=$(pidof usbipd 2>&1 > /dev/null; echo $?)
if [ $USBIPACTIVE -ne 0 ]; then
echo "usbip daemon is not running"
exit 1
fi
if [ "$MODE" == "bind" ]; then
/usr/sbin/usbip bind -b $BUSID
elif [ "$MODE" == "unbind" ]; then
/usr/sbin/usbip unbind -b $BUSID
else
echo "Unknown Mode"
exit 1
fi
- name: Create systemd unit for the zigbee stick
copy:
dest: /etc/systemd/system/[email protected]
owner: root
group: root
mode: u=rw,go=r
content: |
[Unit]
Description=USB/IP Binding on %I
After=network-online.target usbipd.service
Wants=network-online.target
Requires=usbipd.service
[Service]
Type=simple
ExecStart=/usr/sbin/usbip-bind-vendorproduct %i bind
RemainAfterExit=yes
ExecStop=/usr/sbin/usbip-bind-vendorproduct %i unbind
Restart=on-failure
[Install]
WantedBy=multi-user.target
- name: Create systemd unit for usbip
copy:
dest: /etc/systemd/system/usbipd.service
owner: root
group: root
mode: u=rw,go=r
content: |
[Unit]
Description=USB/IP server
After=network.target
[Service]
ExecStart=/usr/sbin/usbipd
[Install]
WantedBy=multi-user.target
- name: Enable and start usbip dongle
systemd:
daemon_reload: yes
enabled: yes
name: "usbip-device@{{usbvendorid}}-{{usbproductid}}"
- name: Enable and start usbipd service
systemd:
daemon_reload: yes
enabled: yes
name: usbipd
state: started
- name: Create udev hotplug trigger script
copy:
dest: /usr/sbin/usbip-hotplug-trigger
owner: root
group: root
mode: u=rwx,go=r
content: |
#!/bin/bash
/usr/sbin/usbip-bind-vendorproduct ${ID_VENDOR_ID}:${ID_MODEL_ID} bind
- name: Create udev rule for hotplug dongle
copy:
dest: /etc/udev/rules.d/usbip.rules
owner: root
group: root
mode: u=rw,go=r
content: |
ACTION=="add", ATTR{idVendor}=="{{usbvendorid}}", ATTR{idProduct}=="{{usbproductid}}", RUN+="/usr/sbin/usbip-hotplug-trigger"
register: dongleudevrule
- name: Restart udev
service:
name: udev
state: restarted
when: dongleudevrule.changed
Now the problem: The implementation of the communication between the usb device and hass is not reconnect-safe. If there is any issue with the communication between, the container needs to be restarted.
I am able to bring the usb device into the container with cgroup rules, so that I can hotplug:
docker create \
--init \
--name "hass" \
--hostname hass \
--network hassinternal \
--device-cgroup-rule='c 166:0 rmw' \
-v /containerdata/homeassistant:/config \
-v /etc/localtime:/etc/localtime:ro \
homeassistant/home-assistant:latest
Now I can start the container and add the device right afterwards. (If this works without any issues, I would create an extension of the container image and to the creation of the usb device via startup script.)
usbip attach -r dashboard -b 1-1.4
docker start hass
docker exec hass mknod /dev/ttyACM0 c 166 0
Until this point, it works without any problems. But if I detach the device or if there is a network issue, hass throws an exception and the implementation is broken until I restart the container:
2020-04-05 09:58:14 WARNING (MainThread) [zigpy_deconz.api] No response to 'Command.write_parameter' command
2020-04-05 09:58:14 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/zigpy_deconz/zigbee/application.py", line 36, in _reset_watchdog
await self._api.write_parameter(NetworkParameter.watchdog_ttl, 3600)
File "/usr/local/lib/python3.7/site-packages/zigpy_deconz/api.py", line 210, in _command
return await asyncio.wait_for(fut, timeout=COMMAND_TIMEOUT)
File "/usr/local/lib/python3.7/asyncio/tasks.py", line 449, in wait_for
raise futures.TimeoutError()
concurrent.futures._base.TimeoutError
2020-04-05 09:58:49 WARNING (MainThread) [zigpy_deconz.api] No response to 'Command.aps_data_request' command
Is there any way to make this more stable?
It would be awesome to have an reconnect mechanism in hass. I’ve already looked into the code, but my python skills are not strong enough.
Kind Regards
Christian