Перейти к основному содержимому

NapiWRT - инструкция по сборке OpenWrt

· 8 мин. чтения
dmn
maintainer

Пошаговая инструкция по сборке кастомной прошивки OpenWrt для плат NapiLab Napi-C/P/Slot на базе Rockchip RK3308 и компьютеров на основе Napi-C (FCC3308, FCM3308). Включает все необходимые патчи, DTS, uci-defaults скрипты и конфигурацию. Особенно интересно для устройств с двумя Ethernet (FCM3308).


1. Подготовка хост-машины (Ubuntu/Debian)

sudo apt install build-essential clang flex bison g++ gawk gcc-multilib \
gettext git libncurses-dev libssl-dev python3-distutils python3-setuptools \
python3-pyelftools swig rsync unzip zlib1g-dev help2man

2. Клонирование OpenWrt

git clone https://github.com/openwrt/openwrt.git ~/openwrt
cd ~/openwrt
./scripts/feeds update -a
./scripts/feeds install -a

3. Кастомизации

Все изменения вносятся в чистое дерево OpenWrt. Ниже - полный список файлов и модификаций.

3.1. Патч U-Boot defconfig

Файл: package/boot/uboot-rockchip/patches/108-board-rockchip-add-napilab-napic.patch

Этот патч добавляет defconfig для NapiLab Napi-C в систему сборки U-Boot. Копируется из репозитория napi-openwrt-build.

3.2. Блок U-Boot в Makefile

В файле package/boot/uboot-rockchip/Makefile нужно добавить определение устройства napic и включить его в список целей сборки.

Добавить блок определения после U-Boot/rock-pi-s-rk3308 (перед строкой # RK3328 boards):

define U-Boot/napic-rk3308
$(U-Boot/rk3308/Default)
DEPENDS+=+PACKAGE_u-boot-$(1):trusted-firmware-a-rk3308-tpl-rock-pi-s
TPL:=rk3308_ddr_589MHz_uart0_m0_v2.10.bin
NAME:=NapiLab Napi-C
BUILD_DEVICES:= \
napilab_napic
endef

Команда для вставки:

sed -i '/^# RK3328 boards/i\
define U-Boot/napic-rk3308\
$(U-Boot/rk3308/Default)\
DEPENDS+=+PACKAGE_u-boot-$(1):trusted-firmware-a-rk3308-tpl-rock-pi-s\
TPL:=rk3308_ddr_589MHz_uart0_m0_v2.10.bin\
NAME:=NapiLab Napi-C\
BUILD_DEVICES:= \\\
napilab_napic\
endef' package/boot/uboot-rockchip/Makefile

Ограничить UBOOT_TARGETS только платами RK3308 (иначе сборка падает на отсутствующих ATF для rk3399 и др.):

Найти блок UBOOT_TARGETS := \ и заменить весь список на:

UBOOT_TARGETS := \
rock-pi-s-rk3308 \
napic-rk3308

3.3. Профиль устройства в armv8.mk

В файл target/linux/rockchip/image/armv8.mk добавить в конец:

define Device/napilab_napic
$(Device/rk3308)
DEVICE_VENDOR := NapiLab
DEVICE_MODEL := Napi-C
DEVICE_DTS := rk3308-napi-c
SUPPORTED_DEVICES := napilab,napic radxa,rockpis
BOOT_SCRIPT := rock-pi-s
UBOOT_DEVICE_NAME := napic-rk3308
DEVICE_PACKAGES := \
kmod-rtw88-8723ds \
kmod-usb-net-cdc-ncm \
kmod-usb-net-rndis \
wpad-basic-mbedtls \
kmod-fs-ext4 \
kmod-fs-msdos \
kmod-fs-vfat \
kmod-fs-exfat \
kmod-fs-ntfs3 \
kmod-usb-storage \
kmod-usb-serial-option \
kmod-usb-net-qmi-wwan \
uqmi \
minicom \
htop \
nano \
lsblk \
usbutils \
tcpdump \
ethtool \
mosquitto \
mosquitto-client \
screen \
xz-utils \
mbpoll \
openssh-sftp-server \
bash \
luci-ssl \
luci-theme-openwrt-2020 \
luci-proto-qmi \
luci-app-mbusd
endef
TARGET_DEVICES += napilab_napic

3.4. Device Tree (DTS)

Файл: target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3308-napi-c.dts

Основан на Rock Pi S, ключевые отличия:

  • uart1, uart2, uart4 включены (RS-485, дополнительные последовательные порты)
  • Bluetooth не используется (занимает UART)
  • USB OTG контроллер (usb20_otg) переключён в режим host - без этого USB-устройства на OTG-порте не видны:
&usb20_otg {
dr_mode = "host";
status = "okay";
};

Важно: без dr_mode = "host" модуль dwc2 инициализируется в режиме gadget и USB-устройства (например, USB-Ethernet адаптеры) не определяются.

3.5. Пакеты

Скопировать в package/:

  • package/luci-app-mbusd/ - LuCI веб-интерфейс для Modbus-шлюза mbusd
  • package/luci-app-mbpoll/ - LuCI веб-интерфейс для Modbus-поллера
  • package/mbscan/ - утилита сканирования Modbus RTU шины (содержит Makefile + src/mbscan.c)

3.6. Файлы прошивки (files/)

Все файлы в директории files/ копируются в корневую ФС образа при сборке.

files/etc/shadow

Пароль root по умолчанию (хеш генерируется через openssl passwd -6):

HASH=$(openssl passwd -6)
echo "root:${HASH}:19000:0:99999:7:::" > files/etc/shadow
chmod 640 files/etc/shadow

files/etc/banner

Кастомный баннер при логине.

files/etc/profile.d/10-sysinfo.sh

Скрипт вывода информации об интерфейсах при логине:

#!/bin/sh

echo ""
for iface in /sys/class/net/eth*; do
name=$(basename "$iface")
ip=$(ip -4 addr show "$name" 2>/dev/null | awk '/inet /{print $2}')
state=$(cat "$iface/operstate" 2>/dev/null)
[ -z "$ip" ] && ip="no address"
echo " $name: $ip ($state)"
done
echo ""

files/etc/uci-defaults/

Скрипты первого старта (выполняются один раз и удаляются):

СкриптНазначение
70-rootpt-resizeРасширяет раздел до конца носителя, перезагружает
80-rootfs-resizeРасширяет ФС через losetup + resize2fs, перезагружает
91-bashМеняет shell root на /bin/bash
92-timezoneУстанавливает MSK-3 (Москва)
93-console-passwordВключает пароль на серийной консоли ttyS0
94-macaddrГенерирует стабильный MAC из OTP-памяти чипа
95-networkНастройка сети: 1 интерфейс - DHCP; 2 - eth0=lan/static, eth1=wan/dhcp; 3+ - eth2+ в бридж с eth0
96-hostnameУстанавливает hostname napiwrt
97-luci-themeТема openwrt-2020
98-firewall-wanЗона wan + правила SSH/HTTP/HTTPS на wan
99-dhcpОтключает DHCP-сервер если одна сетевуха
70-rootpt-resize
if [ ! -e /etc/rootpt-resize ] \
&& type parted > /dev/null \
&& lock -n /var/lock/root-resize
then
ROOT_BLK="$(readlink -f /sys/dev/block/"$(awk -e \
'$9=="/dev/root"{print $3}' /proc/self/mountinfo)")"
ROOT_DISK="/dev/$(basename "${ROOT_BLK%/*}")"
ROOT_PART="${ROOT_BLK##*[^0-9]}"
parted -f -s "${ROOT_DISK}" resizepart "${ROOT_PART}" 100%
mount_root done
touch /etc/rootpt-resize
reboot
fi
exit 1
80-rootfs-resize
if [ ! -e /etc/rootfs-resize ] \
&& [ -e /etc/rootpt-resize ] \
&& type losetup > /dev/null \
&& type resize2fs > /dev/null \
&& lock -n /var/lock/root-resize
then
ROOT_BLK="$(readlink -f /sys/dev/block/"$(awk -e \
'$9=="/dev/root"{print $3}' /proc/self/mountinfo)")"
ROOT_DEV="/dev/${ROOT_BLK##*/}"
LOOP_DEV="$(losetup -f)"
losetup "${LOOP_DEV}" "${ROOT_DEV}"
resize2fs -f "${LOOP_DEV}"
losetup -d "${LOOP_DEV}"
mount_root done
touch /etc/rootfs-resize
reboot
fi
exit 1
91-bash
#!/bin/sh
sed -i 's|/bin/ash|/bin/bash|' /etc/passwd
92-timezone
#!/bin/sh
uci set system.@system[0].timezone='MSK-3'
uci set system.@system[0].zonename='Europe/Moscow'
uci commit system
93-console-password
#!/bin/sh
uci set system.@system[0].ttylogin='1'
uci commit system
94-macaddr
#!/bin/sh
MAC=$(cat /sys/bus/nvmem/devices/rockchip-otp0/nvmem | md5sum | \
sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*/02:\1:\2:\3:\4:\5/')
uci set network.lan.macaddr="$MAC"
uci commit network
95-network
#!/bin/sh

# Убираем дефолтный бридж
uci -q delete network.@device[0]

if [ -e /sys/class/net/eth1 ]; then
# Две+ сетевухи: eth0=lan, eth1=wan

# Собираем дополнительные интерфейсы (eth2, eth3...) в бридж
EXTRA=""
for iface in /sys/class/net/eth*; do
name=$(basename "$iface")
[ "$name" = "eth0" ] && continue
[ "$name" = "eth1" ] && continue
EXTRA="$EXTRA $name"
done

if [ -n "$EXTRA" ]; then
# Есть eth2+ : бридж из eth0 + extras
uci set network.br_lan=device
uci set network.br_lan.type='bridge'
uci set network.br_lan.name='br-lan'
uci add_list network.br_lan.ports='eth0'
for iface in $EXTRA; do
uci add_list network.br_lan.ports="$iface"
done
uci set network.lan.device='br-lan'
else
# Только eth0 + eth1
uci set network.lan.device='eth0'
fi

uci set network.lan.proto='static'
uci set network.lan.ipaddr='192.168.1.1/24'

# wan = eth1 (USB сетевушка)
uci -q delete network.wan
uci set network.wan=interface
uci set network.wan.device='eth1'
uci set network.wan.proto='dhcp'
else
# Одна сетевуха: lan=dhcp
uci set network.lan.device='eth0'
uci set network.lan.proto='dhcp'
uci -q delete network.lan.ipaddr
fi

uci commit network
96-hostname
#!/bin/sh
uci set system.@system[0].hostname='napiwrt'
uci commit system
97-luci-theme
#!/bin/sh
uci set luci.main.mediaurlbase='/luci-static/openwrt-2020'
uci commit luci
98-firewall-wan
#!/bin/sh
[ -e /sys/class/net/eth1 ] || exit 0

# Включаем masq на дефолтной зоне wan (если ещё не включён)
uci set firewall.@zone[1].masq='1'
uci set firewall.@zone[1].mtu_fix='1'
uci commit firewall

# SSH на wan
uci add firewall rule
uci set firewall.@rule[-1].name='Allow-SSH-WAN'
uci set firewall.@rule[-1].src='wan'
uci set firewall.@rule[-1].dest_port='22'
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].target='ACCEPT'

# HTTP на wan
uci add firewall rule
uci set firewall.@rule[-1].name='Allow-HTTP-WAN'
uci set firewall.@rule[-1].src='wan'
uci set firewall.@rule[-1].dest_port='80'
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].target='ACCEPT'

# HTTPS на wan
uci add firewall rule
uci set firewall.@rule[-1].name='Allow-HTTPS-WAN'
uci set firewall.@rule[-1].src='wan'
uci set firewall.@rule[-1].dest_port='443'
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].target='ACCEPT'

uci commit firewall
99-dhcp
#!/bin/sh
if [ ! -e /sys/class/net/eth1 ]; then
# Одна сетевуха - не раздаём DHCP
uci set dhcp.lan.ignore='1'
fi
uci commit dhcp

4. Конфигурация (.config)

Скопировать .config из репозитория napi-openwrt-build. Обязательные пакеты, которые должны быть включены:

CONFIG_PACKAGE_kmod-usb-dwc2=y
CONFIG_PACKAGE_kmod-usb-net-smsc95xx=y
CONFIG_PACKAGE_kmod-usb-gadget=y
CONFIG_PACKAGE_kmod-lib-crc16=y
CONFIG_PACKAGE_kmod-net-selftests=y
CONFIG_PACKAGE_kmod-phy-smsc=y
CONFIG_PACKAGE_parted=y
CONFIG_PACKAGE_losetup=y
CONFIG_PACKAGE_resize2fs=y
CONFIG_PACKAGE_luci-app-mbusd=y
CONFIG_PACKAGE_luci-app-mbpoll=y
CONFIG_PACKAGE_mbscan=y
CONFIG_PACKAGE_mbusd=y
CONFIG_PACKAGE_mbpoll=y

Добавление пакетов в .config (никогда не запускать make menuconfig или make defconfig после!):

for pkg in kmod-usb-dwc2 kmod-usb-net-smsc95xx kmod-usb-gadget kmod-lib-crc16 \
kmod-net-selftests kmod-phy-smsc parted losetup resize2fs; do
sed -i "/$pkg/d" .config
echo "CONFIG_PACKAGE_$pkg=y" >> .config
done

5. Сборка

Первая сборка

cd ~/openwrt
make package/boot/arm-trusted-firmware-rockchip/compile -j$(nproc)
make package/boot/uboot-rockchip/compile -j$(nproc)
make -j$(nproc) EXTRA_IMAGE_NAME=$(date +%d%b_%H%M)

Пересборка после изменений

make -j$(nproc) EXTRA_IMAGE_NAME=$(date +%d%b_%H%M)

Результат

bin/targets/rockchip/armv8/openwrt-ДАТА-rockchip-armv8-napilab_napic-ext4-sysupgrade.img.gz

6. Прошивка

gunzip openwrt-*-napilab_napic-ext4-sysupgrade.img.gz
dd if=openwrt-*-napilab_napic-ext4-sysupgrade.img of=/dev/sdX bs=4M status=progress
sync

Внимательно проверьте /dev/sdX командой lsblk перед записью!


7. Первый запуск

При первом старте на новом носителе происходит:

  1. Скрипт 70-rootpt-resize расширяет раздел - перезагрузка
  2. Скрипт 80-rootfs-resize расширяет ФС - перезагрузка
  3. Остальные uci-defaults применяются - система готова

Итого: две автоматических перезагрузки, после чего устройство полностью настроено.

Доступ

ПараметрОдна сетевухаДве сетевухи
eth0 (LAN)DHCP-клиент192.168.1.1 (static)
eth1 (WAN)-DHCP-клиент
SSHroot@IProot@IP (lan и wan)
LuCIhttp://IP/http://IP/ (lan и wan)
КонсольttyS0, 1500000 бодttyS0, 1500000 бод

8. Перенос сборки на другую машину

Переносить нужно только кастомизации, не build_dir/staging_dir. Список файлов для переноса:

tar czf napi-custom.tar.gz \
.config \
files/ \
target/linux/rockchip/files/ \
target/linux/rockchip/image/armv8.mk \
package/boot/uboot-rockchip/patches/108-board-rockchip-add-napilab-napic.patch \
package/boot/uboot-rockchip/Makefile \
package/luci-app-mbusd/ \
package/luci-app-mbpoll/ \
package/mbscan/

На новой машине:

git clone https://github.com/openwrt/openwrt.git ~/openwrt
cd ~/openwrt
./scripts/feeds update -a
./scripts/feeds install -a
tar xzf napi-custom.tar.gz
make -j$(nproc) EXTRA_IMAGE_NAME=$(date +%d%b_%H%M)

Важно: armv8.mk и package/boot/uboot-rockchip/Makefile из архива перезапишут оригинальные файлы OpenWrt. Если версия OpenWrt сильно отличается, эти файлы нужно мержить вручную - добавлять блоки napic в актуальные файлы новой версии.


9. Известные грабли

  • Никогда не запускать make menuconfig или make defconfig после ручной правки .config - они перезапишут кастомные записи
  • Добавление пакетов: только через sed -i '/<pkg>/d' .config && echo 'CONFIG_PACKAGE_<pkg>=y' >> .config
  • build_dir/staging_dir содержат абсолютные пути - при переносе между машинами всегда make distclean (сохранив .config)
  • UBOOT_TARGETS нужно ограничивать - без ATF для rk3399/rk3588 сборка U-Boot падает
  • dr_mode = "host" обязателен для USB OTG - без него dwc2 уходит в gadget-режим
  • files/etc/shadow не должен содержать пароль в открытом виде - только хеш через openssl passwd -6
  • USB-Ethernet (SMSC LAN9500) требует пакеты: kmod-usb-dwc2, kmod-usb-net-smsc95xx, kmod-usb-gadget, kmod-lib-crc16, kmod-net-selftests, kmod-phy-smsc

Лицензия

GPL-2.0 - следуем лицензии OpenWrt. Все кастомизации открыты.

Debian для NAPI-C с ядром 6.6

· 3 мин. чтения
dmn
maintainer

Сборочная система для создания готового образа Debian (trixie) под одноплатный компьютер Napi-C на базе Rockchip RK3308.

Готовые образы для прошивки и исходный код

Готовые образы: https://download.napilinux.ru/linuximg/napic/debian/

Наисвежайшая информация м сборочная система на GitHub: https://github.com/lab240/napi-debian-build

Подключённые репозитории

  • http://deb.debian.org/debian - основной Debian
  • https://deb.napilab.net - пакеты NapiLab (ядра, утилиты)
  • https://repo.napilab.ru - дополнительные пакеты (mbusd и др.)

Что в образе

  • Debian trixie (arm64)
  • Ядро 6.6.x (vendor Rockchip, собирается из исходников или устанавливается из deb)
  • U-Boot 2023.10
  • Поддержка Device Tree Overlays (UART, I2C, USB host, SPI и др.)
  • Автоматическое расширение раздела при первом запуске

Предустановленные пакеты

Базовые: ssh, NetworkManager, sudo, nano, curl, ntpsec, initramfs-tools, locales

Из packages.list: vim, net-tools, can-utils, mbpoll, minicom, tcpdump, screen, memtester, xxd, tree, util-linux-extra, mosquitto, mosquitto-clients, i2c-tools, python3-pymodbus, python3-pip, python3-smbus2, git, tmux, make, cmake, gcc, build-essential, flex, bison, libssl-dev, pkg-config, mbusd

Overlays по умолчанию

rk3308-uart0 rk3308-uart1 rk3308-uart2-m0 rk3308-uart3-m0
rk3308-i2c1-ds1338 rk3308-i2c3-m0 rk3308-usb20-host

Настраиваются в /boot/uEnv.txt.

Учётные данные

  • root / napilinux
  • napi / napilinux (sudo)

Быстрый старт

Сборка образа из готовых deb

Положить deb-пакеты ядра в kernel-rk-6.6/ и запустить:

sudo ./mkimg.sh

Готовый образ появится в artifacts-trixie/.

Сборка с компиляцией ядра из исходников

sudo ./mkimg.sh --build-kernel

Исходники клонируются из https://gitlab.nnz-ipc.net/pub/napilinux/kernel.git (ветка rk-6.6), собранные deb сохраняются в kernel-rk-6.6/.

Только сборка ядра (без образа)

sudo bash -c '
source config.sh
BUILD_KERNEL=1
source scripts/00-build-kernel.sh
'

Прошивка на SD-карту

xzcat artifacts-trixie/Debian-napilab_*.img.xz | sudo dd of=/dev/sdX bs=4M status=progress
sync

Параметры сборки

Аргументы командной строки

АргументОписание
--build-kernelСобрать ядро из исходников
--branch=rk-6.6Ветка ядра (каталог kernel-<branch>)
--skip-ubootНе прошивать U-Boot в образ
--skip-xzНе сжимать образ (для отладки)

Переменные окружения

ПеременнаяПо умолчаниюОписание
KERNEL_VER6.6.89Версия ядра
IMAGE_SIZE2048Размер образа в MB
DISTRIBUTIONtrixieРелиз Debian
EXTRA_PKGS-Дополнительные пакеты через запятую
HOSTNAME_TARGETnapicHostname платы

Примеры:

# Другая ветка ядра
sudo ./mkimg.sh --branch=rk-6.1

# Увеличенный образ с доп. пакетами
sudo IMAGE_SIZE=4096 EXTRA_PKGS=docker.io ./mkimg.sh

Структура проекта

make-napi-debian/
├── config.sh # конфигурация сборки
├── mkimg.sh # главный скрипт
├── packages.list # дополнительные пакеты
├── napi-archive-keyring.asc # ключ репозитория deb.napilab.net
├── u-boot-latest_*.deb # U-Boot
├── kernel-rk-6.6/ # deb-пакеты ядра
│ ├── linux-image-*.deb
│ └── linux-headers-*.deb
├── scripts/
│ ├── 00-build-kernel.sh # [опц.] сборка ядра из исходников
│ ├── 01-create-image.sh # создание образа и разметка
│ ├── 02-debootstrap.sh # установка базовой системы
│ ├── 03-install-kernel.sh # установка ядра и DTB
│ ├── 04-boot-config.sh # boot.cmd, uEnv.txt, boot.scr
│ ├── 05-configure.sh # пользователи, locale, репы, пакеты
│ ├── 06-cleanup.sh # размонтирование
│ └── 07-install-uboot.sh # прошивка U-Boot, упаковка xz
├── cache/apt/ # кеш скачанных пакетов
└── artifacts-trixie/ # готовые образы

Настройка на плате

Overlays

Редактировать /boot/uEnv.txt, строка overlays=. Доступные overlays:

ls /boot/dtbs/overlay/rk3308/

После изменения перезагрузить плату.

Обновление ядра

При установке нового ядра через dpkg -i linux-image-*.deb автоматически обновляются DTB, симлинки и boot.scr (через postinst hook /etc/kernel/postinst.d/zz-napi-update-boot)

Контакты

Вопросы, заказ плат и интеграция: dj.novikov@gmail.com

Установка USB WIFI RTL8188 в Armbian

· 1 мин. чтения
dmn
maintainer

Адаптер: Realtek RTL8188FTV (ID 0bda:f179)
Драйвер: rtl8xxxu (mainline)
Прошивка: rtlwifi/rtl8188fufw.bin

1. Установить необходимые пакеты

apt update
apt install linux-firmware
apt install zstd

2. Распаковать прошивку

Пакет linux-firmware содержит прошивку в сжатом виде (.zst), но ядро Armbian по умолчанию не поддерживает автоматическую распаковку (CONFIG_FW_LOADER_COMPRESS_ZSTD не включён). Распаковываем вручную:

zstd -d /lib/firmware/rtlwifi/rtl8188fufw.bin.zst

3. Загрузить драйвер

modprobe rtl8xxxu

Если драйвер уже был загружен ранее (с ошибкой), перезагрузить:

rmmod rtl8xxxu && modprobe rtl8xxxu

4. Добавить драйвер в автозагрузку

echo "rtl8xxxu" >> /etc/modules

После этого драйвер будет загружаться автоматически при каждой загрузке системы.

5. Проверить

root@napic:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: end0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether ea:b2:0d:09:2e:6c brd ff:ff:ff:ff:ff:ff
inet 192.168.30.64/24 metric 100 brd 192.168.30.255 scope global dynamic end0
valid_lft 46523sec preferred_lft 46523sec
inet6 fe80::e8b2:dff:fe09:2e6c/64 scope link
valid_lft forever preferred_lft forever
3: wlx6c60ebe162ef: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 6c:60:eb:e1:62:ef brd ff:ff:ff:ff:ff:ff

NAPI2 поддерживает OpenWRT

· 4 мин. чтения
dmn
maintainer

OpenWrt для NapiLab NAPI2 (RK3568): готовая прошивка и сборка из исходников

NapiWRT теперь поддерживает NAPI2 на базе Rockchip RK3568. Готовый образ можно скачать и залить на SD\eMMC, а можно собрать самостоятельно из открытых исходников.


Что такое NAPI2

NAPI2 - промышленный IoT-шлюз NapiLab на базе Rockchip RK3568:

ПараметрЗначение
CPUCortex-A55 × 4, 2.0 ГГц
RAM4 ГБ DDR4
Хранилище32 ГБ eMMC + SD
Ethernet2× Gigabit (LAN + WAN + NAT)
USBUSB 2.0 + USB 3.0 OTG
RS-485UART7, аппаратный RTS
CANCAN 2.0
HDMIHDMI 2.0, framebuffer-консоль
RTCDS1338

Два гигабитных порта и NAT означают, что NAPI2 может одновременно работать как маршрутизатор между промышленной и офисной сетью и как Modbus TCP шлюз.


Скачать готовый образ

Готовые образы публикуются в GitHub Releases:

github.com/lab240/napi-openwrt-build/releases

Файл прошивки: openwrt-rockchip-armv8-napi2-rk3568-ext4-sysupgrade.img.gz

Запись на eMMC

eMMC - встроенная память, напрямую с хост-машины к ней не подключиться. Процедура прошивки:

  1. Загрузите NAPI2 с SD-карты (любой рабочий Linux-образ)
  2. Подключите USB-накопитель с файлом прошивки
  3. Смонтируйте USB и запишите образ на eMMC:
# Монтируем USB-накопитель
mount /dev/sda1 /mnt

# Распаковываем образ
gunzip /mnt/openwrt-rockchip-armv8-napi2-rk3568-ext4-sysupgrade.img.gz

# Записываем на eMMC
dd if=/mnt/openwrt-rockchip-armv8-napi2-rk3568-ext4-sysupgrade.img \
of=/dev/mmcblk0 bs=4M status=progress
sync
  1. Выключите устройство, извлеките SD-карту, подайте питание - NAPI2 загрузится с eMMC.

Проверьте имя устройства eMMC через lsblk - обычно это /dev/mmcblk0.

После подачи питания система автоматически расширит корневой раздел на весь носитель (две перезагрузки при первом старте), применит все настройки и будет готова к работе.

Доступ по умолчанию

ПараметрЗначение
LANeth0, 192.168.1.1 (статика)
WANeth1, DHCP
Веб-интерфейсhttp://192.168.1.1/
SSHroot@192.168.1.1
КонсольttyS2, 1 500 000 бод + HDMI

Что в прошивке из коробки

Прошивка содержит полный промышленный стек:

  • Modbus TCP - mbusd + luci-app-mbusd (RS-485 на UART7 → TCP)
  • Modbus-сканер - mbpoll + luci-app-mbpoll, mbscan
  • MQTT - mosquitto уже настроен и запущен
  • Метрики - collectd с модулями mqtt, rrdtool, modbus
  • 1-Wire - owfs / owserver для датчиков DS18B20
  • I2C / GPIO - i2c-tools, gpiod-tools
  • LTE - поддержка Quectel EP06 через QMI
  • HDMI-консоль - лог ядра и вход на мониторе, USB-клавиатура

NAPI2 также готова к запуску Zigbee2MQTT - 4 ГБ RAM более чем достаточно. Готовый архив Z2M для musl/aarch64 есть в тех же Releases.


Ключевые возможности

  • Двойной Ethernet - eth0 (LAN, 192.168.1.1) + eth1 (WAN, DHCP). NAT и маршрутизация настроены из коробки.
  • HDMI-консоль - framebuffer + DRM VOP2 встроены в ядро. Лог загрузки выводится одновременно на serial и HDMI. Подключите монитор и USB-клавиатуру - полноценный локальный доступ без USB-UART адаптера.
  • RS-485 с аппаратным RTS - UART7 (/dev/ttyS7) с автоматическим управлением направлением передачи. Не нужен GPIO для переключения приём/передача.
  • CAN 2.0 - для подключения промышленного оборудования с CAN-интерфейсом.
  • RTC DS1338 - аппаратные часы на I2C5, время сохраняется при выключении питания.
  • MAC из eMMC CID - стабильный MAC-адрес генерируется из уникального идентификатора eMMC (отдельные MAC для LAN и WAN).

Собрать самостоятельно

Исходники кастомизаций открыты:

github.com/lab240/napi-openwrt-build

Зависимости

sudo apt install build-essential clang flex bison g++ gawk gcc-multilib \
gettext git libncurses-dev libssl-dev python3-distutils python3-setuptools \
python3-dev python3-pyelftools rsync swig unzip zlib1g-dev

Сборка

# Клонируем OpenWrt
git clone https://github.com/openwrt/openwrt.git
cd openwrt
./scripts/feeds update -a
./scripts/feeds install -a

# Накладываем кастомизации NAPI2
cp -r /path/to/napi-openwrt/napi2-files/* .

# Применяем конфиг ядра для HDMI
bash apply-kernel-config.sh

# Сначала собираем NanoPi R5S (нужен U-Boot от того же RK3568)
echo 'CONFIG_TARGET_rockchip_armv8_DEVICE_friendlyarm_nanopi-r5s=y' >> .config
make defconfig
make -j$(nproc)

# Переключаемся на NAPI2
sed -i '/DEVICE_friendlyarm_nanopi-r5s/d' .config
echo 'CONFIG_TARGET_rockchip_armv8_DEVICE_napi2-rk3568=y' >> .config
make defconfig
make -j$(nproc)

Результат появится в bin/targets/rockchip/armv8/.

Первая сборка занимает 30–60 минут (компилируется тулчейн). Пересборка с изменениями - 5–10 минут.

Зачем двухэтапная сборка

U-Boot для RK3568 берётся из конфигурации NanoPi R5S - это та же SoC, и defconfig от FriendlyElec хорошо поддерживается в апстриме OpenWrt. Первый проход собирает U-Boot, второй - финальный образ с нашим Device Tree и конфигурацией.


Ссылки

Утилита modbus-slave. Эмулятор Modbus RTU датчиков

· 4 мин. чтения
dmn
maintainer

Когда разрабатываешь систему мониторинга или SCADA, часто нужно протестировать опрос датчиков — но реального оборудования под рукой нет. Или нужно показать демо заказчику без физических устройств. Или хочется отладить логику мастера не выезжая на объект.

Именно для этого мы написали modbus_slave — эмулятор Modbus RTU slave устройств на C, который работает на Linux и Windows, не требует зависимостей и умеет отдавать реальные данные из файлов.

Что умеет

  • Эмулирует до 30 независимых Modbus RTU устройств на одном последовательном порту
  • Каждое устройство отвечает на FC03 (Read Holding Registers), 20 регистров
  • Значения регистров — случайные (для тестирования) или из файла (реальные данные)
  • Работает на Linux x86_64, aarch64 (NAPI2, RK3568, Raspberry Pi) и Windows x64
  • Поддерживает RS-485 — RTS direction control через DTS/GPIO
  • Режим демона с логами в syslog и systemd service для автозапуска
  • Защита от устаревших данных — если скрипт-источник упал, регистры возвращают нули
  • Один статический бинарь без зависимостей — скопировал и запустил

Архитектура

Идея простая: один процесс слушает RS-485 шину и отвечает на запросы от любого из 30 адресов. Для мастера это выглядит как несколько физических устройств на линии — он не видит разницы.

Мастер (PC/ПЛК)          RS-485 шина          modbus_slave (NAPI2)
mbpoll -a 1 ──────────────────────────► slave ID 1 → /tmp/cpu.dat
mbpoll -a 2 ──────────────────────────► slave ID 2 → /tmp/time.dat
mbpoll -a 3 ──────────────────────────► slave ID 3 → random

Регистры читаются из обычных текстовых файлов — одно число на строку. Файлы живут в /tmp (tmpfs — RAM диск), SD карта не изнашивается.

Быстрый старт

Установка на NAPI2 / aarch64

# Скачать статический бинарь
wget https://github.com/lab240/modpoll-slave/raw/main/bin/modbus_slave_aarch64
chmod +x modbus_slave_aarch64

# Запустить — 3 датчика, порт ttyS7
./modbus_slave_aarch64 -p /dev/ttyS7 -b 115200 -a 3

Запуск на Windows

modbus_slave.exe -p COM4 -b 115200 -a 3

Проверка с mbpoll

mbpoll -m rtu -b 115200 -P none -a 1 -r 1 -c 20 /dev/ttyUSB0

Реальные данные из файлов

Самое интересное — каждому slave можно привязать файл с реальными значениями. Формат простой: одно целое число на строку.

# Запуск: slave 1 читает данные CPU, slave 2 — время, slave 3 — рандом
./modbus_slave -p /dev/ttyS7 -b 115200 -a 3 \
-f 1:/tmp/cpu.dat \
-f 2:/tmp/time.dat

Файл обновляется внешним скриптом атомарно через mv — никакой гонки данных:

# Температура ядер CPU (°C × 100)
while true; do
for zone in /sys/class/thermal/thermal_zone*/temp; do
val=$(( $(cat $zone) / 10 ))
echo $val
done > /tmp/cpu.tmp
mv /tmp/cpu.tmp /tmp/cpu.dat
sleep 5
done

Значение 4523 в регистре означает 45.23 °C — стандартное соглашение для передачи дробных чисел в Modbus.

Защита от падения скриптов

Если скрипт обновления данных упал — файл перестаёт обновляться, но данные в нём остаются старые. Мастер продолжал бы читать устаревшие значения.

Параметр -t задаёт максимальный возраст файла:

./modbus_slave -p /dev/ttyS7 -b 115200 -f 1:/tmp/cpu.dat -t 10

Если файл не обновлялся более 10 секунд — все регистры возвращают 0. Это сразу видно мастеру и SCADA системе. В лог пишется предупреждение (не чаще раза в минуту чтобы не спамить):

file /tmp/cpu.dat is stale (45s > 10s), returning zeros

По умолчанию t=10. Отключить: -t 0.

Режим демона и systemd

# Запуск как демон
./modbus_slave -d -p /dev/ttyS7 -b 115200 -a 3 -f 1:/tmp/cpu.dat

# Статус
./modbus_slave -s

# Остановка
./modbus_slave -k

# Логи
journalctl -t modbus_slave -f

Для автозапуска при загрузке — systemd service:

sudo cp modbus_slave /usr/local/bin/
sudo cp service/modbus_slave.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now modbus_slave

Статистика запросов пишется в лог каждые 60 секунд:

stats: slaves=3 total_ok=12480 total_err=0

Windows

На Windows всё то же самое, только порт называется COM4 вместо /dev/ttyS7, файлы в C:\temp\ вместо /tmp/, и -bg вместо -d:

modbus_slave.exe -bg -p COM4 -b 115200 -a 3 ^
-f 1:C:\temp\cpu.dat ^
-f 2:C:\temp\time.dat

Логи пишутся в C:\temp\modbus_slave.log.

Сборка из исходников

Код разделён на три файла:

ФайлСодержимое
modbus_core.hВся логика Modbus: CRC16, FC03, файлы, статистика
modbus_slave.cLinux: termios, fork, syslog, /proc
modbus_slave_win.cWindows: Win32 API, CreateFile, DCB
# Linux x86_64
gcc -O2 -Wall -o modbus_slave src/modbus_slave.c

# Linux aarch64 статический
aarch64-linux-gnu-gcc -O2 -Wall -static -o modbus_slave_aarch64 src/modbus_slave.c

# Windows .exe с Linux
x86_64-w64-mingw32-gcc -O2 -Wall -o modbus_slave.exe src/modbus_slave_win.c

Итог

modbus_slave решает конкретную задачу — дать возможность разрабатывать и тестировать Modbus мастер без реального железа, или превратить одноплатный компьютер в многоканальный шлюз данных в Modbus.

Код открытый, собирается одной командой, работает на том же NAPI2 где и всё остальное.

Репозиторий: github.com/lab240/modpoll-slave

Утилита mbscan - быстрый поиск Modbus устройств на линии

· 5 мин. чтения
dmn
maintainer

247 адресов за 2.5 секунды, ноль зависимостей, один .c файл. Рассказываем, зачем мы написали свой сканер Modbus-шины и как он работает.


Проблема: «а что вообще висит на шине?»

Кто работал с Modbus RTU, знает ситуацию: подключаешь шлюз к RS-485 шине, а там десяток устройств с неизвестными адресами. Или один датчик, но кто-то поставил ему адрес 117 вместо документированного 1. Или устройство просто не отвечает — и непонятно, проблема в адресе, скорости, чётности или в самом устройстве.

Стандартный подход - mbpoll или любой Modbus-клиент, которым вручную перебираешь адреса. Это работает, но медленно и неудобно: 247 возможных адресов, на каждый нужно отправить запрос, подождать таймаут, проверить ответ.

Мы решили автоматизировать это одной утилитой.

Репозиторий: github.com/lab240/mbscan


Что такое mbscan

mbscan - консольная утилита для сканирования Modbus RTU шины. Открывает последовательный порт, последовательно опрашивает диапазон адресов функцией FC03 (Read Holding Registers) и выводит найденные устройства с содержимым регистров.

Один файл на C, никаких внешних библиотек. Встроенная реализация CRC16, POSIX-совместимый код. Работает на Linux x86_64, aarch64, OpenWrt, Raspberry Pi — везде, где есть терминальный API POSIX.

Быстрый старт

# Сканируем всё на /dev/ttyUSB0 (по умолчанию: 115200-8N1, таймаут 100мс)
mbscan -p /dev/ttyUSB0

# Быстрый скан с таймаутом 10мс
mbscan -p /dev/ttyUSB0 -o 10

# Конкретный диапазон, читаем 4 регистра
mbscan -p /dev/ttyUSB0 -f 1 -t 30 -c 4

# 9600 бод, чётность 8E1
mbscan -p /dev/ttyS1 -b 9600 -d 8E1

Вывод выглядит так:

mbscan: scanning /dev/ttyUSB0 115200-8N1, addresses 1-247, timeout 100ms
mbscan: reading 4 register(s) starting at 0

Found slave 125: [0]=125 [1]=1 [2]=830 [3]=794

mbscan: done. Found 1 device(s).

Нашёл устройство на адресе 125, прочитал 4 регистра — готово.


Параметры

mbscan -p PORT [опции]

-p PORT Последовательный порт (обязательный)
-b BAUD Скорость (по умолчанию: 115200)
-d PARAMS Формат данных: 8N1, 8E1, 8O1, 7E1 и т.д. (по умолчанию: 8N1)
-f FROM Начальный адрес (по умолчанию: 1)
-t TO Конечный адрес (по умолчанию: 247)
-o MS Таймаут на адрес в мс (по умолчанию: 100)
-r REG Начальный регистр, 0-based (по умолчанию: 0)
-c COUNT Количество регистров для чтения (по умолчанию: 1)
-v Подробный вывод
-h Справка

Как это работает внутри

Алгоритм прямолинейный, но дьявол в деталях:

  1. Открывает последовательный порт, настраивает скорость, чётность, количество стоп-битов через termios.

  2. Для каждого адреса в диапазоне:

    • Сбрасывает буфер порта от предыдущих данных
    • Формирует 8-байтовый запрос Modbus RTU FC03 с CRC16
    • Отправляет запрос и ждёт ответ с настроенным таймаутом
    • Валидирует ответ: проверяет CRC, адрес slave, код функции
    • Если всё сходится — выводит найденное устройство с содержимым регистров
  3. Между запросами выдерживает межкадровую паузу Modbus (3.5 символьных времени) — это требование протокола, без него устройства могут путать конец одного кадра и начало другого.

CRC16 реализован встроенный - нет зависимости от libmodbus или других библиотек. Весь код в одном файле mbscan.c.


Скорость сканирования

Скорость определяется таймаутом на адрес. Если устройство не отвечает — ждём полный таймаут. Если отвечает — переходим к следующему сразу после получения ответа.

ТаймаутПолный скан (1–247)Когда использовать
10 мс~2.5 секКороткие кабели, лабораторный стенд
50 мс~12 секБольшинство установок
100 мс~25 секПо умолчанию, надёжно
200 мс~50 секДлинные линии RS-485

На практике 10 мс хватает для стенда с коротким кабелем. Для промышленных линий с десятками метров RS-485 лучше ставить 50-100 мс — на длинных линиях задержки растут из-за переотражений и ёмкости кабеля.


Сборка

Нативная компиляция

cd src
gcc -O2 -Wall -o mbscan mbscan.c

Статическая сборка (один бинарник без зависимостей):

gcc -O2 -Wall -static -o mbscan mbscan.c

Пакет для OpenWrt

Каталог mbscan кладётся в дерево пакетов OpenWrt:

cp -r mbscan /path/to/openwrt/package/
cd /path/to/openwrt
echo "CONFIG_PACKAGE_mbscan=y" >> .config
make package/mbscan/compile -j$(nproc)

Результат — .ipk (или .apk) пакет в bin/packages/*/base/.

Кросс-компиляция для aarch64

Если есть тулчейн OpenWrt:

/path/to/openwrt/staging_dir/toolchain-aarch64_generic_gcc-*/bin/aarch64-openwrt-linux-gcc \
-O2 -Wall -static -o mbscan-linux-aarch64 src/mbscan.c

Готовые бинарники для x86_64 и aarch64 доступны на странице Releases.


Интеграция с luci-app-mbpoll

mbscan - не просто самостоятельная утилита. Он используется как бэкенд для вкладки Scan Bus в веб-интерфейсе luci-app-mbpoll - нашем LuCI-приложении для опроса Modbus-устройств.

Схема простая: пользователь задаёт параметры порта и диапазон адресов в браузере, LuCI вызывает mbscan на устройстве, парсит вывод и отображает найденные устройства в таблице. Не нужно заходить по SSH, не нужно помнить синтаксис - всё через веб-интерфейс.

Репозиторий luci-app-mbpoll: github.com/lab240/luci-app-mbpoll


Где используется

Основная платформа - промышленные IoT-шлюзы NapiLab Napi на базе Rockchip RK3308 под управлением OpenWrt. Napi имеет встроенный RS-485 на /dev/ttyS1 и два USB-порта для дополнительных адаптеров — типичная конфигурация для Modbus-шлюза.

Но mbscan работает на любом Linux с последовательным портом: обычный x86_64 с USB-RS485 адаптером (CH341, CP2102, FTDI), Raspberry Pi, любая embedded-плата.


Лицензия

GPL-2.0 - как и OpenWrt, как и остальные наши инструменты.

Сборка и запуск Zigbee2mqtt для OpenWRT

· 5 мин. чтения
dmn
maintainer

Инструкция по сборке Zigbee2MQTT под musl/aarch64 на хост-машине с Docker и запуску на OpenWrt.


Почему это нетривиально

OpenWrt использует musl libc вместо стандартного glibc. Это означает:

  • Официальные бинарники Node.js с nodejs.org (glibc) не запустятся
  • Пакет node в фидах OpenWrt — только host-инструмент для сборки (PKG_HOST_ONLY=1), в прошивку не попадает
  • Entware для aarch64 не содержит Node.js
  • Нативные модули (@serialport/bindings-cpp) нужно компилировать под musl

Решение: собирать всё в Docker-контейнере на базе Alpine Linux (тоже использует musl).


Требования

Железо

Проверено на NAPI-C (rk3308\512Мб\4Гб Nand) c прошивкой OpenWRT (NapiWRT). Репозиторий: https://github.com/lab240/napi-openwrt-build/

Программное обеспечение на хост-машине

  • Docker
  • Git

Шаг 1: Подготовка носителя

OpenWrt по умолчанию создаёт rootfs раздел ~104 МБ. Для Zigbee2MQTT нужно минимум 500 МБ свободного места.

В нашей сборке NapiWRT это решено автоматически через два uci-defaults скрипта которые при первой загрузке расширяют rootfs до конца носителя:

files/etc/uci-defaults/70-rootpt-resize — расширяет раздел и перезагружается:

if [ ! -e /etc/rootpt-resize ] \
&& type parted > /dev/null \
&& lock -n /var/lock/root-resize
then
ROOT_BLK="$(readlink -f /sys/dev/block/"$(awk -e \
'$9=="/dev/root"{print $3}' /proc/self/mountinfo)")"
ROOT_DISK="/dev/$(basename "${ROOT_BLK%/*}")"
ROOT_PART="${ROOT_BLK##*[^0-9]}"
echo "70-rootpt-resize: expanding ${ROOT_DISK} partition ${ROOT_PART} to 100%..."
parted -f -s "${ROOT_DISK}" resizepart "${ROOT_PART}" 100%
echo "70-rootpt-resize: done, rebooting..."
mount_root done
touch /etc/rootpt-resize
reboot
fi
exit 1

files/etc/uci-defaults/80-rootfs-resize — расширяет файловую систему через losetup и перезагружается:

if [ ! -e /etc/rootfs-resize ] \
&& [ -e /etc/rootpt-resize ] \
&& type losetup > /dev/null \
&& type resize2fs > /dev/null \
&& lock -n /var/lock/root-resize
then
ROOT_BLK="$(readlink -f /sys/dev/block/"$(awk -e \
'$9=="/dev/root"{print $3}' /proc/self/mountinfo)")"
ROOT_DEV="/dev/${ROOT_BLK##*/}"
echo "80-rootfs-resize: resizing filesystem on ${ROOT_DEV}..."
LOOP_DEV="$(losetup -f)"
losetup "${LOOP_DEV}" "${ROOT_DEV}"
resize2fs -f "${LOOP_DEV}"
losetup -d "${LOOP_DEV}"
echo "80-rootfs-resize: done, rebooting..."
mount_root done
touch /etc/rootfs-resize
reboot
fi
exit 1

Необходимые пакеты в .config сборки:

CONFIG_PACKAGE_parted=y
CONFIG_PACKAGE_losetup=y
CONFIG_PACKAGE_resize2fs=y

Шаг 2: Установка Node.js на устройство

Node.js для musl/aarch64 предоставляет проект unofficial-builds от nodejs.org.

# На устройстве
cd /tmp
wget https://unofficial-builds.nodejs.org/download/release/v22.22.0/node-v22.22.0-linux-arm64-musl.tar.gz
mkdir -p /opt/node
tar xzf node-v22.22.0-linux-arm64-musl.tar.gz -C /opt/node --strip-components=1
rm node-v22.22.0-linux-arm64-musl.tar.gz

# Добавляем в PATH
export PATH=/opt/node/bin:$PATH

# Проверяем
node --version # v22.22.0
npm --version # 10.9.4

Официальный бинарник с nodejs.org (linux-arm64 без суффикса musl) не запустится — он скомпилирован под glibc.


Готовый архив

Если вы не хотите собирать самостоятельно — готовый архив Zigbee2MQTT для musl/aarch64 доступен в релизах репозитория:

👉 https://github.com/lab240/napi-openwrt-build/releases

Скачайте файл zigbee2mqtt-2.9.1-openwrt-aarch64-musl.tar.gz и перейдите сразу к Шагу 4.


Шаг 3: Сборка Zigbee2MQTT на хост-машине

Zigbee2MQTT содержит нативные модули (@serialport/bindings-cpp) которые нужно компилировать под целевую платформу. Делаем это в Docker с Alpine (musl) под arm64.

На хост-машине

# Клонируем репозиторий
git clone --depth 1 https://github.com/Koenkk/zigbee2mqtt.git ~/zigbee2mqtt-arm

# Собираем в Docker под Alpine/arm64/musl
docker run --rm -v ~/zigbee2mqtt-arm:/app \
--platform linux/arm64 \
node:22-alpine \
sh -c "apk add python3 make g++ linux-headers && \
cd /app && \
npm install && \
npm rebuild @serialport/bindings-cpp --build-from-source && \
npm run build && \
tar czf /app/z2m.tar.gz --dereference -C /app ."

Ключевые флаги:

  • --platform linux/arm64 — целевая архитектура aarch64
  • node:22-alpine — Alpine использует musl как OpenWrt, Node.js 22 соответствует требованиям Z2M
  • linux-headers — нужны для компиляции @serialport/bindings-cpp
  • --build-from-source — компилируем нативные модули вместо использования prebuilt glibc бинарников
  • --dereference — разворачиваем симлинки в tar (иначе они сломаются при распаковке)

Время сборки: 3–5 минут.


Шаг 4: Копирование на устройство

# С устройства (через scp)
scp dmn@<IP_ХОСТА>:~/zigbee2mqtt-arm/z2m.tar.gz /opt/

# Распаковываем
rm -rf /opt/zigbee2mqtt
mkdir /opt/zigbee2mqtt
tar xzf /opt/z2m.tar.gz -C /opt/zigbee2mqtt/
rm /opt/z2m.tar.gz

Шаг 5: Зависимости на устройстве

Для работы нативных модулей нужна libstdc++:

apk update
apk add libstdcpp6

Шаг 6: Запуск

export PATH=/opt/node/bin:$PATH
cd /opt/zigbee2mqtt
npm start

При успешном запуске:

Starting Zigbee2MQTT without watchdog.
Onboarding page is available at http://0.0.0.0:8080/

Откройте браузер: http://<IP_устройства>:8080/ — онбординг страница для настройки координатора и MQTT.


Конфигурация

После онбординга конфиг сохраняется в /opt/zigbee2mqtt/data/configuration.yaml:

mqtt:
server: mqtt://localhost # Mosquitto уже установлен в базовой сборке Napi
serial:
port: /dev/ttyUSB0 # Порт Zigbee-координатора
adapter: ember # или znp — зависит от координатора

Автозапуск через procd

Создаём init-скрипт /etc/init.d/zigbee2mqtt:

#!/bin/sh /etc/rc.common

START=99
STOP=10
USE_PROCD=1

start_service() {
procd_open_instance
procd_set_param env PATH=/opt/node/bin:/usr/sbin:/usr/bin:/sbin:/bin
procd_set_param command /opt/node/bin/node /opt/zigbee2mqtt/index.js
procd_set_param dir /opt/zigbee2mqtt
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
}
chmod +x /etc/init.d/zigbee2mqtt
/etc/init.d/zigbee2mqtt enable
/etc/init.d/zigbee2mqtt start

Итог: что получили

КомпонентВерсияИсточник
Node.js22.22.0unofficial-builds.nodejs.org (musl/arm64)
npm10.9.4в составе Node.js
Zigbee2MQTT2.9.xсобран в Docker/Alpine/arm64
libstdc++из репозитория OpenWrtapk
Mosquittoиз образавстроен в сборку Napi

Известные ограничения

  • Node.js не входит в стандартный образ — устанавливается вручную в /opt
  • При обновлении прошивки /opt сохраняется (на отдельном разделе или eMMC)
  • udevadm недоступен — автообнаружение адаптера не работает, порт указывается вручную в конфиге

OpenWrt для Napi - архитектура и сборка

· 12 мин. чтения
dmn
maintainer

Статья для тех, кто хочет собрать OpenWrt под платы NapiLab Napi самостоятельно и понимать, что именно происходит на каждом шаге — от патча U-Boot до первого входа по SSH.


Зачем вообще собирать OpenWrt для Napi?

NapiLab Napi — промышленный одноплатный компьютер (SBC) и системный модуль (SOM) на базе Rockchip RK3308. Платформа ориентирована на промышленный IoT: сбор данных с датчиков, шлюзы Modbus TCP/RTU, MQTT-брокеры, удалённый мониторинг.

Ванильный OpenWrt доступен для "родственной" платы RockPi-S, но не знает особенностей Napi: нет device tree дополнительных портов, нет правильной конфигурации U-Boot, нет пакетов для промышленного применения. Наш репозиторий — это набор патчей, DTS, uci-defaults и пакетов, которые превращают чистый снапшот OpenWrt в готовый промышленный одноплатник.

Если хотите сразу попробовать без сборки — готовые образы доступны на странице загрузок napiworld.ru.

Что даёт кастомная сборка

  • Стабильный MAC-адрес — генерируется из OTP-данных чипа, не меняется после перезагрузки
  • Правильный Device Tree — UART1 и UART2 в нужных режимах, Bluetooth отключён
  • Готовый стек Modbus TCPmbusd + веб-интерфейс luci-app-mbusd из коробки
  • MQTT-брокерmosquitto уже установлен и настроен
  • Поддержка LTE-модемов — Quectel EP06 работает без дополнительных танцев
  • Первый старт без консоли — все настройки применяются через uci-defaults автоматически

Поддерживаемое железо

Все платы используют один и тот же SoC — Rockchip RK3308, поэтому собирается одна прошивка для всей линейки:

ПлатаХранилищеТип
NapiLab Napi-C4 ГБ NAND — 32 ГБ eMMCПромышленный SBC
NapiLab Napi-P4 ГБ NAND — 32 ГБ eMMCПромышленный SBC
NapiLab Napi-Slot4 ГБ NAND — 32 ГБ eMMCSOM
Radxa ROCK Pi SРеференсная плата, тот же RK3308

Характеристики RK3308

КомпонентДетали
CPUQuad-core ARM Cortex-A35, 1.3 ГГц
RAM256 МБ / 512 МБ DDR3
Ethernet100 Мбит/с (GMAC + PHY RTL8201F)
USB2× USB 2.0 Host
UART3× UART (ttyS0 — консоль, ttyS1, ttyS2)
Wi-FiRTL8723DS (802.11b/g/n)

Структура репозитория: что куда кладётся

./
├── files/
│ └── etc/
│ └── uci-defaults/ # Скрипты первого старта
│ ├── 91-bash
│ ├── 92-timezone
│ ├── 93-console-password
│ ├── 94-macaddr
│ ├── 95-network
│ ├── 96-hostname
│ ├── 97-luci-theme
│ └── 99-dhcp

├── package/
│ ├── boot/
│ │ └── uboot-rockchip/
│ │ ├── Makefile
│ │ └── patches/
│ │ └── 108-board-rockchip-add-napilab-napic.patch # Патч U-Boot
│ └── luci-app-mbusd/ # Веб-интерфейс для mbusd
│ ├── Makefile
│ ├── htdocs/luci-static/resources/view/
│ │ └── mbusd.js
│ └── root/
│ ├── etc/uci-defaults/luci-app-mbusd
│ └── usr/share/
│ ├── luci/menu.d/luci-app-mbusd.json
│ └── rpcd/acl.d/luci-app-mbusd.json

└── target/linux/rockchip/
├── files/arch/arm64/boot/dts/rockchip/
│ └── rk3308-napi-c.dts # Кастомный Device Tree
└── image/
└── armv8.mk # Описание целевого образа

Разберём каждую часть подробно.


U-Boot: почему нужен патч и что он делает

OpenWrt собирает U-Boot из исходников вместе с прошивкой. Для RK3308 есть готовая конфигурация для Radxa ROCK Pi S — мы взяли её за основу, так как схемотехника близка к Napi.

Патч 0001-napic-rk3308-defconfig.patch

Патч добавляет новый вариант napic-rk3308 в систему сборки U-Boot:

+++ b/configs/napic-rk3308_defconfig
@@ -0,0 +1,42 @@
+CONFIG_ARM=y
+CONFIG_ARCH_ROCKCHIP=y
+CONFIG_SYS_TEXT_BASE=0x00600000
+CONFIG_ROCKCHIP_RK3308=y
+CONFIG_TARGET_EVB_RK3308=y
+CONFIG_DEFAULT_DEVICE_TREE="rk3308-napi-c"
+CONFIG_DISTRO_DEFAULTS=y
+CONFIG_SYS_MALLOC_F_LEN=0x4000
+CONFIG_BAUDRATE=1500000
+CONFIG_BOOTDELAY=0
...

Ключевые настройки:

  • CONFIG_DEFAULT_DEVICE_TREE="rk3308-napi-c" — указываем U-Boot использовать наш DTS
  • CONFIG_BAUDRATE=1500000 — нестандартная скорость консоли (1.5 Мбод), типичная для Rockchip
  • CONFIG_BOOTDELAY=0 — не ждём прерывания при старте (промышленное применение)

Как собрать только U-Boot

make package/boot/uboot-rockchip/compile VARIANT=napic-rk3308 -j$(nproc)

Флаг VARIANT=napic-rk3308 говорит системе сборки использовать именно наш defconfig.


Device Tree (DTS): описываем железо ядру

Device Tree — это описание аппаратной конфигурации платы в текстовом формате. Ядро Linux не знает про периферию «само по себе», ему нужно явно сказать: «вот тут UART, вот тут Ethernet, вот GPIO».

Файл rk3308-napi-c.dts

Берём за основу rk3308-rock-pi-s.dts (Radxa ROCK Pi S — ближайший аналог по схемотехнике) и переопределяем то, что отличается у Napi.

/dts-v1/;
#include "rk3308.dtsi"
#include "rk3308-rock-pi-s.dtsi"

/ {
model = "NapiLab Napi-C";
compatible = "napilab,napi-c", "rockchip,rk3308";
};

/* UART1 → RS-485 через mbusd */
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart1_xfer>;
};

/* UART2 — доступен как /dev/ttyS2 */
&uart2 {
status = "okay";
};

/* Bluetooth отключаем — не нужен в промышленном применении */
&bluetooth {
status = "disabled";
};

Что важно в этом DTS:

uart1 — маппится на /dev/ttyS1. Это главный последовательный порт, к которому подключаются RS-485 устройства Modbus. mbusd будет слушать именно его.

uart2 — маппится на /dev/ttyS2, доступен для дополнительных устройств.

bluetooth disabled — RTL8723DS предоставляет и Wi-Fi, и Bluetooth через один чип. Bluetooth нам не нужен и только занимает UART, поэтому отключаем на уровне DTS — никаких лишних сервисов, никаких потерь производительности.

Где лежит DTS в дереве OpenWrt

target/linux/rockchip/files/arch/arm64/boot/dts/rockchip/rk3308-napi-c.dts

OpenWrt копирует файлы из target/linux/<arch>/files/ поверх исходников ядра перед компиляцией. Это стандартный механизм добавления новых DTS без форка ядра.


uci-defaults: автоматическая настройка при первом старте

uci-defaults — это скрипты, которые OpenWrt запускает один раз при первой загрузке и затем удаляет. Они позволяют настроить систему до того, как пользователь зашёл в веб-интерфейс или по SSH.

Скрипты лежат в:

target/linux/rockchip/armv8/base-files/etc/uci-defaults/

Нумерация определяет порядок выполнения. Разберём каждый:


91-bash — bash как оболочка по умолчанию

#!/bin/sh
# Меняем /bin/ash на /bin/bash для root
sed -i 's|/bin/ash|/bin/bash|' /etc/passwd

По умолчанию OpenWrt использует ash (BusyBox). Для работы с промышленными скриптами, которые рассчитаны на bash-синтаксис (массивы, [[, $RANDOM, process substitution), нужен настоящий bash. Скрипт делает одно изменение в /etc/passwd.


92-timezone — московское время

#!/bin/sh
uci set system.@system[0].timezone='MSK-3'
uci set system.@system[0].zonename='Europe/Moscow'
uci commit system

Промышленные устройства работают в конкретном часовом поясе. Временна́я метка в логах и данных должна быть правильной сразу, без ручной настройки. MSK-3 — это UTC+3 (Москва).


93-console-password — пароль на серийную консоль

#!/bin/sh
# Включаем запрос пароля на ttyS0
uci set system.@system[0].ttylogin='1'
uci commit system

По умолчанию OpenWrt пускает на консоль без пароля — удобно при разработке, неприемлемо в продакшне. Скрипт включает запрос пароля на ttyS0 (консоль 1.5 Мбод).


94-macaddr — стабильный MAC из OTP

Это самый важный скрипт. Проблема: у RK3308 нет встроенного уникального MAC-адреса в eFuse — он генерируется случайно при каждой загрузке. Это катастрофа для промышленного применения: DHCP-сервер каждый раз выдаёт другой IP, ARP-таблицы засоряются, устройство теряется в сети.

Решение: генерировать MAC детерминированно из OTP (One-Time Programmable) памяти чипа. OTP содержит уникальные данные, которые прошиваются на заводе и никогда не меняются.

#!/bin/sh

# Читаем OTP и берём MD5 от него
MAC=$(cat /sys/bus/nvmem/devices/rockchip-otp0/nvmem | md5sum | \
sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\).*/02:\1:\2:\3:\4:\5/')

# Применяем MAC к интерфейсу
uci set network.@device[0].macaddr="$MAC"
uci commit network

Разбор команды по частям:

  1. /sys/bus/nvmem/devices/rockchip-otp0/nvmem — бинарный файл с содержимым OTP через интерфейс nvmem ядра
  2. md5sum — хешируем бинарные данные, получаем 32 hex-символа
  3. sed — берём первые 12 символов и форматируем как MAC
  4. Первый байт 02 — бит Local (bit 1 = 1) установлен, бит Multicast (bit 0 = 0) сброшен. Это стандарт для locally-administered MAC

Результат: каждая плата Napi получает один и тот же MAC при каждой загрузке, но разные платы имеют разные MAC — уникальность гарантирована уникальностью OTP.


95-network — настройка Ethernet без бриджа

#!/bin/sh

# Убираем дефолтный бридж br-lan
uci set network.lan.device='eth0'
uci set network.lan.type=''
uci delete network.@bridge-vlan[0] 2>/dev/null

uci commit network

Стандартный OpenWrt создаёт бридж br-lan из всех Ethernet-портов — это логично для роутера с несколькими портами. У Napi один Ethernet-порт, бридж избыточен. Скрипт переводит lan напрямую на eth0, убирая лишний сетевой уровень.


96-hostname — имя устройства

#!/bin/sh
uci set system.@system[0].hostname='napiwrt'
uci commit system

napiwrt — имя по умолчанию. Устройство будет видно в сети как napiwrt.local (через mDNS). Пользователь может сменить имя через LuCI.


97-luci-theme — тема веб-интерфейса

#!/bin/sh
uci set luci.main.mediaurlbase='/luci-static/openwrt-2020'
uci commit luci

Тема openwrt-2020 — современный Bootstrap-based интерфейс. Тема bootstrap (старая) выглядит устаревшей. Устанавливаем сразу нужную.


99-dhcp — конфигурация DHCP

#!/bin/sh

# Убираем dnsmasq с lan-интерфейса — устройство само получает IP по DHCP
uci set dhcp.lan.ignore='1'
uci commit dhcp

Napi в типовой конфигурации — не роутер, а промышленный шлюз. Он не должен раздавать DHCP в сеть, он должен получать IP сам. Скрипт отключает DHCP-сервер на lan.


Пакеты: что и зачем включено в сборку

Промышленный стек

ПакетНазначение
mbusdШлюз Modbus RTU → Modbus TCP. Слушает /dev/ttyS1 (RS-485) и пробрасывает на TCP-порт
luci-app-mbusdВеб-интерфейс для mbusd: старт/стоп, конфигурация порта, мониторинг
mbpollCLI-инструмент для опроса Modbus-устройств с командной строки
mosquittoMQTT-брокер. Устройства публикуют данные в топики, приложения подписываются
mosquitto-clientCLI-клиент: mosquitto_pub и mosquitto_sub для отладки

Поддержка USB-Serial адаптеров

kmod-usb-serial-ch341   # WCH CH340/CH341 (самые распространённые)
kmod-usb-serial-cp210x # Silicon Labs CP2102 и серия
kmod-usb-serial-ftdi # FTDI FT232 и совместимые
kmod-usb-serial-pl2303 # Prolific PL2303

Napi имеет 2× USB 2.0. Через USB-Serial можно подключить дополнительные RS-485/RS-232 адаптеры или устройства с USB-интерфейсом.

Поддержка LTE

kmod-usb-net-qmi-wwan   # QMI-протокол для LTE-модемов
uqmi # Пользовательский инструмент для управления QMI

Поддержка Quectel EP06 (Cat-6 LTE). Модем подключается через USB, управляется через QMI. uqmi позволяет настроить APN, поднять PPP-соединение, смотреть сигнал.

Сетевые инструменты

openssh-sftp-server   # SFTP — копирование файлов через SSH без FTP
luci-ssl-wolfssl # HTTPS для LuCI (wolfSSL — лёгкая альтернатива OpenSSL)
tcpdump # Захват трафика прямо на устройстве
ethtool # Диагностика Ethernet

Административные утилиты

bash    # Полноценная оболочка
htop # Мониторинг процессов
nano # Редактор для тех, кто не любит vi
screen # Мультиплексор терминала — незаменим при работе через последовательный порт

luci-app-mbusd: веб-интерфейс для Modbus-шлюза

Пакет luci-app-mbusd — наша собственная разработка. mbusd — отличный Modbus-шлюз, но управляется только через конфиг-файл и командную строку. Для промышленного применения нужен удобный веб-интерфейс.

Что умеет luci-app-mbusd

  • Запуск / остановка / перезапуск службы mbusd через кнопки в браузере
  • Включение / отключение автозапуска при загрузке
  • Live-статус процесса с отображением реальных параметров запуска
  • Отображение IP-адреса и порта, на котором слушает шлюз
  • Полная конфигурация: последовательный порт, скорость, чётность, стоп-биты, параметры Modbus

Интерфейс написан как стандартное LuCI-приложение на Lua + HTML, следует конвенциям OpenWrt UCl API.


Сборка: пошаговая инструкция

1. Зависимости (Ubuntu/Debian)

sudo apt install build-essential clang flex bison g++ gawk gcc-multilib \
gettext git libncurses-dev libssl-dev python3-distutils rsync unzip zlib1g-dev

2. Клонируем OpenWrt

git clone https://github.com/openwrt/openwrt.git
cd openwrt

# Обновляем фиды (репозитории пакетов)
./scripts/feeds update -a
./scripts/feeds install -a

3. Накладываем кастомизации

Архив с кастомизациями берём из релизов репозитория:

# Распаковываем наш архив поверх дерева OpenWrt
tar xzf napic-openwrt-YYYYMMDD-HHMM-v1.0.tar.gz -C /path/to/openwrt/

Архив содержит все файлы из нашего репозитория в том же дереве каталогов, что и OpenWrt. После распаковки:

  • target/linux/rockchip/ — дополнен нашим DTS и uci-defaults
  • package/boot/uboot-rockchip/patches/ — содержит патч U-Boot
  • package/luci-app-mbusd/ — добавлен наш пакет
  • .config — готовая конфигурация сборки

4. Собираем U-Boot

make package/boot/uboot-rockchip/compile VARIANT=napic-rk3308 -j$(nproc)

U-Boot для Rockchip RK3308 состоит из нескольких стадий:

  • TPL (Tertiary Program Loader) — инициализация DDR
  • SPL (Secondary Program Loader) — инициализация минимального железа
  • U-Boot proper — полноценный загрузчик

Все три стадии собираются автоматически, результат упаковывается в idbloader.img + u-boot.itb.

5. Собираем прошивку

make -j$(nproc)

Система сборки OpenWrt:

  1. Компилирует кросс-тулчейн (gcc, binutils, musl libc)
  2. Компилирует ядро Linux с нашим DTS
  3. Компилирует все выбранные пакеты
  4. Упаковывает rootfs + ядро + U-Boot в финальный образ

Время сборки на современном железе (8 ядер): 30–60 минут при первой сборке, 5–10 минут при пересборке с изменениями.

6. Результат сборки

bin/targets/rockchip/armv8/
└── openwrt-rockchip-armv8-napilab_napic-ext4-sysupgrade.img.gz

Образ содержит таблицу разделов GPT, U-Boot, ядро, rootfs — всё в одном файле.


Прошивка

Если не хотите собирать самостоятельно — готовые образы доступны на странице загрузок napiworld.ru.

# Распаковываем
gunzip openwrt-rockchip-armv8-napilab_napic-ext4-sysupgrade.img.gz

# Пишем на носитель (замените /dev/sdX на реальное устройство!)
dd if=openwrt-rockchip-armv8-napilab_napic-ext4-sysupgrade.img \
of=/dev/sdX \
bs=4M \
status=progress
sync

⚠️ Внимательно проверьте /dev/sdX командой lsblk перед записью. Ошибка в имени устройства приведёт к затиранию данных.


Первый запуск

После записи образа и подачи питания:

  1. U-Boot стартует, инициализирует DDR, находит ядро в разделе
  2. Ядро загружается, парсит наш DTS, инициализирует периферию
  3. OpenWrt init запускает скрипты uci-defaults (один раз)
  4. Устройство получает IP по DHCP (MAC стабилен — DHCP-сервер выдаст тот же IP)
  5. LuCI доступен по http://<IP>/

Параметры доступа по умолчанию

ПараметрЗначение
IPDHCP (стабильный MAC гарантирует постоянный lease)
Веб-интерфейсhttp://<IP>/ → LuCI
SSHroot@<IP> (пароль не установлен, задаётся при первом входе)
КонсольttyS0, 1 500 000 бод

Типичные вопросы

Почему скорость консоли 1.5 Мбод?

Это стандарт Rockchip для отладочных UART. На такой скорости загрузочные сообщения U-Boot и ядра отображаются без задержек. Требуется адаптер USB-UART с поддержкой нестандартных скоростей (CP2102, FTDI — работают, CH340 — часто нет).

Почему за основу взяли ROCK Pi S, а не официальный RK3308 EVB?

ROCK Pi S — хорошо поддерживаемая в апстриме OpenWrt плата на RK3308. Её конфигурация U-Boot и DTS проверены сообществом, регулярно обновляются. EVB (Evaluation Board от Rockchip) в OpenWrt поддерживается хуже.

Можно ли добавить свои пакеты?

Да. Добавьте пакеты в .config (через make menuconfig или напрямую) и пересоберите. Кастомный пакет можно положить в package/ или добавить внешний фид.

Как обновить прошивку через LuCI?

System → Backup / Flash Firmware → Flash new firmware image. Загрузите sysupgrade.img.gz. OpenWrt сохранит пользовательские настройки (/etc/config/) если не снять галочку «Keep settings».


Скриншоты

Командная строка через консоль, ssh

Главная страница

Настройка сети

Обновление, бекап, рестор

Пакеты

Mbusd

Настройка и тестирование CAN интерфейса в Linux

· 1 мин. чтения
dmn
maintainer

Настройка CAN интерфейса

Поднимаем и проверяем CAN интерфейс.

Интересно, что CAN в Linux это сетевой интерфейс. На него нельзя повесить IP, но Linux управлять можно через ip link.

ip link set can0 down
ip link set can0 type can bitrate 500000 restart-ms 100
ip link set can0 up

Проверка loopback

Проверяем loop (Аналог ping localhost).

Ставим пакет:

apt install can-utils

Тестирование

В одной сессии слушаем can0:

root@napi2:~# candump -L can0

В другой на этот же интерфейс шлем посылку:

cansend can0 123#11223344

Должны получить ответ в сессии, где слушали:

(1769774861.028890) can0 123#11223344

#can #napi2