Skip to content

Building Pi MotionEye

Kevin Watkins edited this page Feb 25, 2022 · 2 revisions

Building Pi MotionEye

Preparing

  • Install latest version of Raspberry Pi (this guide uses Debian Buster)
  • USB Camera and/or camera that uses the dedicated CSI interface
  • USB Cellular modem supporting US LTE-FDD bands 2,4 etc

Setup remote headless wifi, ssh, and vnc

touch /Volumes/boot/ssh

cat << EOF | sudo tee /Volumes/boot/wpa_supplicant.conf  
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=US
network={
 ssid="pants!"
 psk="Pants!Spoon!"
}
EOF
ssh pi@raspberry.local
sudo apt install -y screen jq
sudo apt update
sudo apt full-upgrade -y
sudo reboot

Setup passwordless logins

 ssh-copy-id pi@raspberrypi.local 

Configure Networks

Setup the various services for networking, including zero-tier, cellular modems, and wifi AP.

Steps using Network Manager (default for Ubuntu 12.01)

nmcli radio wifi
nmcli dev wifi list
nmcli device wifi connect 'SSID' password 'PASSWORD' ifname wlan0
nmcli connection add type wifi con-name 'pants-wlan0' ifname wlan0 ssid 'pants!'
# use nmtui to set the password
nmcli con up id 'pants-wlan0' ifname wlan0
nmcli connection add type wifi con-name "XFINITY-wlan1" ifname wlan1 ssid "XFINITY" -- \
wifi-sec.key-mgmt wpa-eap 802-1x.eap ttls 802-1x.phase2-auth pap \
802-1x.identity 'username' 802-1x.password 'password' 
nmcli connection edit "XFINITY"

nmcli con show 
nmcli con del id 'pants!'

nmcli con up id "XFINITY" ifname wlan1 
ls -l /etc/NetworkManager/system-connections/
nmcli general logging level DEBUG
# For armbian follow https://forum.armbian.com/topic/9698-4g-usb-modem-with-armbian/
# Bit buggy on armbian, had to unplug/replug in different USB port to get it working
# To find primary port, e.g. ttyUSB3
mmcli --list-modems
mmcli --modem 1
sudo nmcli connection edit type gsm con-name "GoogleFi"
# 'set gsm.apn h2g2' then 'save'
nmcli connection add type gsm ifname ttyUSB3 con-name googlefi apn h2g2
nmcli connection up id googlefi
nmcli r wwan on

Removing systemd-networkd and DHCPD

Removing systemd-networkd:

sudo systemctl disable systemd-networkd.service
sudo systemctl mask systemd-networkd.service
sudo systemctl stop systemd-networkd.service
sudo apt-get install network-manager
systemctl stop NetworkManager
systemctl start NetworkManager
systemctl restart systemd-networkd.service

Removing dhcpd from Raspberry Pi OS

sudo echo "interface wlan0" >> /etc/dhcpcd.conf
sudo echo "nohook wpa_supplicant" >> /etc/dhcpcd.conf
sudo echo "interface wlan1ifconfi" >> /etc/dhcpcd.conf
sudo echo "nohook wpa_supplicant" >> /etc/dhcpcd.conf
systemctl restart dhcpcd.service

If you really want to still keep systemd-network

nmcli dev status
nano /etc/NetworkManager/NetworkManager.conf

[keyfile]
unmanaged-devices=interface-name:wlan*

systemctl stop NetworkManager
systemctl start NetworkManager
systemctl restart systemd-networkd.service

Avahi mDNS

Install and add the host name "pants-motioneye" to the configuration files.

sudo apt install -y avahi-daemon avahi-utils
sudo nano /etc/hosts /etc/hostname /etc/avahi/avahi-daemon.conf
sudo reboot 

Verify by running avahi-browse -a and ssh pi@pants-motioneye

Zero-tier

curl -s https://install.zerotier.com | sudo bash
sudo zerotier-cli join d3ecf5726d81bd8e

Install and authorize new node in my.zero-tier.com

The new node should have a manual ip address, bridging enabled, and a manual route to the internal network we will be using as an access point.

Multipath support

echo '{"settings": {"multipathMode": 0}}' > /var/lib/zerotier-one/networks.d/local.conf

Verify by connecting to the pi with the zero-tier address

Cellular Modem

Simply plugging it in should spin up a new interface, usb0. Verify running lsusb and ip link

Example /etc/NetworkManager/system-connections/googlefi.nmconnection for Huawai & Google Fi

[connection]
id=googlefi
uuid=6dd16a78-dcdb-46c9-8378-22ff19fe8dda
type=gsm
interface-name=ttyUSB3
permissions=

[gsm]
apn=h2g2

[ipv4]
dns-search=
method=auto

[ipv6]
addr-gen-mode=stable-privacy
dns-search=
method=auto

[proxy]

Setting up Google Fi on the latest version of Ubuntu running on RPI4:

# mmcli --modem 0
  -----------------------------
  General  |         dbus path: /org/freedesktop/ModemManager1/Modem/0
           |         device id: 98fd7ccfee3a853a21015bc5099ba02dc1721cef
  -----------------------------
  Hardware |      manufacturer: Huawei Technologies Co., Ltd.
           |             model: E397
           | firmware revision: 11.337.35.00.644
           |         supported: gsm-umts, lte
           |           current: gsm-umts, lte
           |      equipment id: 990000791635693
  -----------------------------
  System   |            device: /sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1
           |           drivers: option1
           |            plugin: huawei
           |      primary port: ttyUSB3
           |             ports: ttyUSB0 (at), ttyUSB2 (qcdm), ttyUSB3 (at)
  -----------------------------
  Status   |    unlock retries: sim-pin (3), sim-puk (10), sim-pin2 (1), sim-puk2 (10)
           |             state: searching
           |       power state: on
           |    signal quality: 0% (cached)
  -----------------------------
  Modes    |         supported: allowed: 4g; preferred: none
           |                    allowed: 2g; preferred: none
           |                    allowed: 2g, 4g; preferred: none
           |           current: allowed: 2g, 4g; preferred: none
  -----------------------------
  IP       |         supported: ipv4, ipv6, ipv4v6
  -----------------------------
  3GPP     |              imei: 990000791635693
           |      registration: searching
  -----------------------------
  SIM      |         dbus path: /org/freedesktop/ModemManager1/SIM/0
# mmcli --modem 0 --enable
# mmcli --modem 0 --3gpp-scan --timeout=300
  ---------------------
  3GPP scan | networks: 310260 - name n/a (lte, current)
            |           310120 - name n/a (lte, available)
            |           310830 - name n/a (lte, available)
            |           312530 - name n/a (lte, available)
            |           311882 - name n/a (lte, available)
            |           311480 - name n/a (lte, forbidden)
            |           311490 - name n/a (lte, available)

Configure the Pi as a WiFi Access Point

Follow the guide setting up hostap and dnsmasq. Alternatively use the steps at https://www.raspberryconnect.com/projects/65-raspberrypi-hotspot-accesspoints/158-raspberry-pi-auto-wifi-hotspot-switch-direct-connection to setup fallback as hostspot if unable to reach your AP.

Do NOT use Network Manager, although it is possible - https://unix.stackexchange.com/questions/234552/create-wireless-access-point-and-share-internet-connection-with-nmcli To get it to work, I had to hack in a reoccurring (every 10 minutes) cronjob that reset iptable rules preventing traffic between the network interfaces. These rules appear to originate from Network Manager and not configurable.

AP "pants-motioneye" for 192.168.200.0/24 default routing through gw usb0

Add to the end of sudo nano /etc/dhcpcd.conf

interface wlan0
static ip_address=192.168.200.1/24
nohook wpa_supplicant

Also adding

sudo nano /etc/dnsmasq.conf


interface=wlan0
dhcp-range=192.168.200.10,192.168.200.20,255.255.255.0,infinite
domain=wlan
address=/pants-motioneye.wlan/192.168.200.1

Add a managed route for 192.168.200.1/24 routing through 10.0.1.20 in zero-tier

sudo iptables -t nat -A POSTROUTING -o usb0 -j MASQUERADE
sudo netfilter-persistent save

And then IP forwarding

sudo nano /etc/sysctl.d/routed-ap.conf

net.ipv4.ip_forward=1

Reboot and verify

Configure Cameras

Pi Camera

Verify camera works as-is and the resolution support

v4l2-ctl --list-devices 
vcgencmd get_camera
raspistill -v -o test.jpg

v4l2-ctl -V    
Format Video Capture:
    Width/Height      : 1024/768

If not, install the required packages.

sudo apt install -y raspberrypi-kernel-headers libusb-dev dkms v4l2loopback-utils v4l2loopback-dkms 
sudo apt install -y vlc libjpeg-dev git gstreamer1.0-plugins-good  gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-omx libgstrtspserver-1.0-dev gstreamer1.0-rtsp libglib2.0-dev

Thermal Camera

sudo apt-get install qtcreator qt5-qmake qtbase5-dev qml-module-qtquick-controls2
git clone https://github.com/Kheirlb/purethermal1-uvc-capture
./build_pi_thermal_app.sh
pip3 install opencv-contrib-python==4.1.0.25
#pip3 install opencv-python

Verify lsusb -d 1e4e:0100 -v | grep iProduct iProduct 2 PureThermal (fw:v1.3.0)

cd ./purethermal1-uvc-capture/ui_software/Parabilis_Thermal && sudo python3 ./RecordIR_v18.3.py

Setup motioneye

For Ubuntu follow steps to install. For Raspberry Pi use these steps to install motion with hardware encoded enabled otherwise the fps will crawl to under 5 fps.

Install motioneye using python2.7 as it still doesn't support python3.

Then follow steps configuring and adding motioneye to inits

sudo apt install libcurl4-openssl-dev libssl-dev
sudo python2.7 -m pip install motioneye
sudo mkdir -p /etc/motioneye
cp /usr/local/share/motioneye/extra/motioneye.conf.sample /etc/motioneye/motioneye.conf
mkdir -p /var/lib/motioneye
cp /usr/local/share/motioneye/extra/motioneye.systemd-unit-local /etc/systemd/system/motioneye.service
systemctl daemon-reload 
systemctl enable motioneye
systemctl start motioneye

Test by going to http://pants-motioneye:8765/ as 'admin' and adding the installed cameras. The supported resolution of the camera should align with v4l2-ctl -V e.g 1024x768

Setup video file sharing

To share you have choices, mainly NFS (Unix), SMB (Microsoft), or AFS/Netatalk (Apple).

I'd recommend NFS for reliability, especially if sharing the media to another server like Home Assistant.

NFS

Install steps from https://www.raspberrypi.org/documentation/configuration/nfs.md

sudo apt-get install nfs-kernel-server
mkdir -p /export/media
chmod 777 /export/media /var/lib/motioneye
mount --bind /var/lib/motioneye /export/media

/etc/fstab

/var/lib/motioneye    /export/media   none    bind  0  0

/etc/exports

/export 10.0.0.0/16(rw,fsid=0,insecure,no_subtree_check,async)
/export/media   10.0.0.0/16(rw,nohide,insecure,no_subtree_check,async)

Restart services:

sudo exportfs -ra
sudo systemctl restart nfs-kernel-server

Validate:

showmount -e
Export list for pants-motioneye:
/export/media 10.0.0.0/16
/export       10.0.0.0/16

To automatically mount from another machine: sudo apt install nfs-common mkdir -p /mnt/motioneye && chmod 777 /mnt/motioneye mount -t nfs -o proto=tcp,port=2049 10.0.1.20:/export/media /media/motion/garage

/etc/fstab

10.0.1.20:/export/media /media/motion/garage nfs auto 0 0

AFS/Netatalk

To access the shares go Finder -> Go -> Connect to Server then afp://

sudo apt-get install -y netatalk 
sudo nano /etc/netatalk/afp.conf

[Homes]
  basedir regex = /home
[Pants Motion Eye]
  path = /var/lib/motioneye

Samba

Install:

sudo apt-get install samba samba-common-bin
chmod 1777 /var/lib/motioneye

/etc/samba/smb.conf

[share]
   Comment = Shared Folder
   Path = /var/lib/motioneye
   Browseable = yes
   Writeable = Yes
   only guest = no
   create mask = 0777
   directory mask = 0777
   Public = yes
   Guest ok = yes  

All-in-one rtsp with gstreamer compiling instructions

git clone https://github.com/GStreamer/gst-rtsp-server.git
cd gst-rtsp-server/examples && gcc -o test-launch  test-launch.c  `pkg-config --cflags --libs gstreamer-rtsp-server-1.0`
sudo ./test-launch "( rpicamsrc preview=false bitrate=2000000 keyframe-interval=15 ! video/x-h264, framerate=15/1 ! h264parse ! rtph264pay name=pay0 pt=96 )"

Alternatively, a direct stream can be achieved using steps from https://gist.github.com/lancevalour/397c4e98b6b1e5dc1971

gst-launch-1.0 rpicamsrc preview=false bitrate=2000000 keyframe-interval=15 ! video/x-h264, framerate=15/1 ! h264parse ! rtph264pay name=pay0 pt=96 ! udpsink host=239.1.1.1 port=9002

I'm still getting a low FPS (4-10) and suspect it has to do with the overhead of motion. And it does, see installing a custom ffmpeg with hardware encoding above. https://chriscarey.com/blog/2017/04/30/achieving-high-frame-rate-with-a-raspberry-pi-camera-system/ has another solution making the raspberry pi a simple IP camera. Basically making it available via rtsp://host:8554/stream

nano /home/pi/stream-rtsp.sh 

#!/bin/bash
raspivid -ih -pf -pts -o – -t 0 -w 1280 -h 720 -fps 20 -b 3500000 -g 40 | cvlc -A alsa,none –alsa-audio-device default -vvv stream:///dev/stdin –sout ‘#rtp{access=udp,sdp=rtsp://:8554/stream}’ :demux=h264 –h264-fps=20

chmod 755 /home/pi/stream-rtsp.sh 

To add this to run at startup

nano /etc/systemd/system/stream-rtsp.service

[Unit]
Description=auto start stream
After=multi-user.target    
[Service]
Type=simple
ExecStart=/home/pi/stream-rtsp.sh
User=pi
WorkingDirectory=/home/pi
Restart=on-failure
[Install]
WantedBy=multi-user.target

sudo systemctl enable stream-rtsp.service
sudo systemctl start stream-rtsp.service
sudo systemctl stop stream-rtsp.service
sudo systemctl status stream-rtsp.service

Extras: Power, Stealth, Motion

Most important is that you are feeding enough power to the rig. Use a power adapter that pushes down 5v and at least 2amps. I was getting crashes and random reboots due and needed a better power supply. Debugging this is difficult w/out special hardware although voltage regulating is worth looking at if you experience crashes, more information at https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=147781&start=50#p972790

vcgencmd get_throttled
perl -e 'printf "%08b\n", 0x50005'

If using 4g modem ZTE MF833V it works by creating an usb0 interface. The admin interface can be accessed through pi using ssh forwarding.

ssh -N -L 8011:192.168.0.1:80 pi@pants-motioneye
pip install speedtest-cli && speedtest-cli 

Go incognito turning off the ACT LED, PWR LED, and Ethernet LED

sudo mount -o remount,rw,noatime,sync /boot
sudo nano /boot/config.txt

dtparam=act_led_trigger=none
dtparam=act_led_activelow=off
dtparam=pwr_led_trigger=none
dtparam=pwr_led_activelow=off
dtparam=eth_led0=14
dtparam=eth_led1=14

To send when motion is detected over MQTT to Home Assistant

sudo apt install -y mosquitto-clients 
mosquitto_pub -h m16.cloudmqtt.com -p 13161 -u <user> -P <password> -t cameras/garage/motion -m ON
mosquitto_pub -h m16.cloudmqtt.com -p 13161 -u <user> -P <password> -t cameras/garage/motion -m OFF

And configuring the sensor in Home Assistant

binary_sensor: 
  - platform: mqtt
    name: "Garage Motion"
    state_topic: "cameras/garage/motion"
    payload_on: "ON"
    payload_off: "OFF"
    device_class: "motion"

Extras: Layer 2 Bridging Ethernet and ZeroTier Networks


# https://zerotier.atlassian.net/wiki/spaces/SD/pages/193134593/One+Port+Linux+Bridge
# physical lan: 192.168.86.0/24
# physical lan dhcp range: 192.168.86.20 through 192.168.86.250
# zerotier auto-assign range: 192.168.86.5 through 192.168.86.15
# zerotier managed route: 192.168.86.0/23

# default gw: 192.168.86.1/24
GW_ADDR=192.168.86.1
# bridge ip address: 192.168.86.2/24
BR_ADDR=192.168.86.2

# curl -s https://install.zerotier.com | sudo bash
sudo zerotier-cli join $NETWORK_ID
sudo zerotier-cli listnetworks
sudo zerotier-cli set $NETWORK_ID allowManaged=0

# Interfaces (zerotier and bridging)
NETWORK_ID="3efa5cb78ad237c3"
BR_IF=br0
ZT_IF=ztrfyi5acj

# enable systemd networking 
sudo systemctl enable systemd-networkd
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl enable systemd-resolved
sudo systemctl enable systemd-timesyncd


cat << EOF | sudo tee /etc/systemd/network/25-bridge-br0.network
[Match]
Name=192.168.86.2

[Network]
Address=192.168.86.2
Gateway=192.168.86.1
DNS=1.1.1.1
EOF

cat << EOF | sudo tee /etc/systemd/network/br0.netdev
[NetDev]
Name=br0
Kind=bridge
EOF

cat << EOF | sudo tee /etc/systemd/network/25-bridge-br0-zt.network
[Match]
Name=$ZT_IF

[Network]
Bridge=br0
EOF

cat << EOF | sudo tee /etc/systemd/network/25-bridge-br0-en.network   
[Match]
Name=eth0

[Network]
Bridge=br0
EOF


iptables -A FORWARD -p all -i br0 -j ACCEPT

Next Steps:

systemd-networkd is a different networking beast. Helpful commands,

networkctl list

https://gist.github.com/SimonPe/0fd6ea6fa754aff88d5e

Extras: Capturing & Adding Sound to MotionEye

Instruction at https://community.home-assistant.io/t/adding-audio-to-motioneye-recordings/198438/41 Step include finding the port for the audio input using arecord, creating and updating ffmpeg /data/etc/motioneye-audio.sh to use the correct input port, and finally adding the recording script to motioneye motion actions and storage triggers.

arecord -l
**** List of CAPTURE Hardware Devices ****
  card 2: Device [C-Media USB Audio Device], device 0: USB Audio [USB Audio]
  Subdevices: 0/1
  Subdevice #0: subdevice #0
#results translate to plughw:1,0
arecord --device=plughw:1,0 --format S16_LE --rate 44100 -c1 test.wav
aplay --device=plughw:1,0 test.wav
ffmpeg -f alsa -ac 2 -i plughw:1,0 output.mkv

/data/etc/motioneye-audio.sh

#!/usr/bin/env bash
# Taken from https://community.home-assistant.io/t/adding-audio-to-motioneye-recordings/198438/56
# This script needs to be in your motion conf directory, e.g, /etc/motioneye/motioneye-audio.sh
#
#
# Video Device > Extra Motion Options:
# on_movie_start /etc/motioneye/motioneye-audio.sh start '%$' %f
# File Storage > Run a command:
# /etc/motioneye/motioneye-audio.sh stop '%$' %f
#
# Use 'arecord -l' to find the plughw:x,x for ffmpeg
#
MOTION_CONF_DIR="/etc/motioneye"

operation=$1
camera_name=$2
        echo "camera_name = " $camera_name >> /tmp/script-verification
file_path=$3
        echo "file_path = " $file_path >> /tmp/script-verification
motion_config_dir="$( cd /etc/motioneye >/dev/null 2>&1 && pwd )"
        echo "motion_config_dir = " $motion_config_dir >> /tmp/script-verification
# Replacing with camera name due to cameraID/thread mismatch
# motion_camera_conf="${motion_config_dir}/camera-${camera_id}.conf"
motion_camera_conf="$( egrep -l \^camera_name.${camera_name} ${motion_config_dir}/*.conf)"
        echo "motion_camera_conf = " $motion_camera_conf >> /tmp/script-verification
#netcam="$(if grep -q 'netcam_highres' ${motion_camera_conf};then echo 'netcam_highres'; else echo 'netcam_url'; fi)"
extension="$(echo ${file_path} | sed 's/^/./' | rev | cut -d. -f1  | rev)"

case ${operation} in
    start)
#        credentials="$(grep netcam_userpass ${motion_camera_conf} | sed -e 's/netcam_userpass.//')"
#                echo "credentials = " $credentials >> /tmp/script-verification
#        stream="$(grep ${netcam} ${motion_camera_conf} | sed -e "s/${netcam}.//")"
#                echo "stream = " $stream >> /tmp/script-verification
#        full_stream="$(echo ${stream} | sed -e "s/\/\//\/\/${credentials}@/")"
#                echo "full_stream = " $full_stream >> /tmp/script-verification
        ffmpeg -y -f alsa -i plughw:1,0 -codec:a aac ${file_path}.m4a &
        ffmpeg_pid=$!
        # echo ${ffmpeg_pid} > /tmp/motion-audio-ffmpeg-camera-${camera_id}
        echo ${ffmpeg_pid} > /tmp/motion-audio-ffmpeg-camera-${camera_name}
        ;;

    stop)
        kill -2 $(cat /tmp/motion-audio-ffmpeg-camera-${camera_name})
        rm -rf $(cat /tmp/motion-audio-ffmpeg-camera-${camera_name})

        # Merge the video and audio to a single file, and replace the original video file
        ffmpeg -y -i ${file_path} -i ${file_path}.m4a -c copy ${file_path}.temp.${extension};
        mv -f ${file_path}.temp.${extension} ${file_path};

        # Remove audio file after merging
        rm -f ${file_path}.wav;
        ;;

    *)
        echo "Usage ./motioneye-audio.sh start <camera-name> <full-path-to-moviefile>"
        exit 1
esac

And start and end statements in MotionEyeOS setup:

Video Device > Extra Motion Options: on_movie_start /etc/motioneye/motioneye-audio.sh start '%$' %f

File Storage > Run a command: /etc/motioneye/motioneye-audio.sh stop '%$' %f

Validate:

   cd /etc/motioneye && ./motioneye-audio.sh start camera-1 /var/lib/motioneye/Camera1/2020-09-19/17-17-27.mp4 
   ./motioneye-audio.sh stop camera-1 /var/lib/motioneye/Camera1/2020-09-19/17-17-27.mp4

Extras: Cellular modem (NetworkManager) and Zero-tier (Systemd Networking) Layer 2 Bridge

We will be using systemd networking to control all interfaces other than the cellular modem. We are using NetworkManager for the cellular modem as it is the easiest path to set up and configure. This may require having NetworkManager ignore these interfaces.

Since the cell modem works best with NetworkManager, we will show out to bridge by disabling NetworkManager & also using it. See https://wiki.archlinux.org/index.php/Network_bridge for steps.

For wireless interfaces, it looks like you need ebtables apt install ebtables

Bridging using NetworkManager

First, remove any of the other networking managers, including systemd-networkd

sudo systemctl disable systemd-networkd.service
sudo systemctl mask systemd-networkd.service
sudo systemctl stop systemd-networkd.service
sudo apt-get install network-manager

Configure and setup Network Manager

systemctl stop NetworkManager
systemctl start NetworkManager
systemctl restart systemd-networkd.service
nmcli radio wifi
nmcli device wifi list
nmcli connection add \
type wifi con-name "XFINITY" ifname wlan0 ssid "XFINITY" -- \
wifi-sec.key-mgmt wpa-eap 802-1x.eap ttls 802-1x.phase2-auth pap \
802-1x.identity "kwatkins6" 802-1x.password 'xxx' 

nmcli connection add type bridge ifname br0 stp no

Bridging via systemd-network

nmcli dev status
nano /etc/NetworkManager/NetworkManager.conf

[keyfile]
unmanaged-devices=interface-name:wlan*

systemctl stop NetworkManager
systemctl start NetworkManager
systemctl restart systemd-networkd.service
# Interfaces (zerotier and bridging)
NETWORK_ID="3efa5cb78ad237c3"
# curl -s https://install.zerotier.com | sudo bash
sudo zerotier-cli join $NETWORK_ID
sudo zerotier-cli listnetworks
cat << EOF | sudo tee /etc/systemd/network/br0.netdev
[NetDev]
Name=br0
Kind=bridge
EOF

cat << EOF | sudo tee /etc/systemd/network/25-bridge-br0-zt.network
[Match]
Name=$ZT_IF

[Network]
Bridge=br0
EOF

cat << EOF | sudo tee /etc/systemd/network/25-bridge-br0-ppp.network   
[Match]
Name=ppp0

[Network]
Bridge=br0
EOF