diff --git a/hardware/mox/mox-rainbow-backend/Makefile b/hardware/mox/mox-rainbow-backend/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..eaaada1859a541bba7e9213fdcab7f79d1d2264a --- /dev/null +++ b/hardware/mox/mox-rainbow-backend/Makefile @@ -0,0 +1,37 @@ +# +## Copyright (C) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) +# +## This is free software, licensed under the GNU General Public License v3. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=mox-rainbow-backend +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=CZ.NIC +PKG_LICENSE:=GPL-3.0-or-later +PKG_LICENSE_FILES:=LICENSE + +include $(INCLUDE_DIR)/package.mk + +define Package/mox-rainbow-backend + TITLE:=mox-rainbow-backend + DEPENDS:=\ + @TARGET_mvebu_cortexa53_DEVICE_cznic-mox \ + +rainbow-animator \ + +python3-uci + PROVIDES:=rainbow-backend +endef + +Build/Compile:=: + +define Package/mox-rainbow-backend/install + $(INSTALL_DIR) $(1)/usr/share + $(INSTALL_DATA) ./files/backend.sh $(1)/usr/libexec/rainbow/ + $(INSTALL_DATA) ./files/animator_backend.sh $(1)/usr/libexec/rainbow/ +endef + +$(eval $(call BuildPackage,mox-rainbow-backend)) diff --git a/hardware/mox/mox-rainbow-backend/files/animator_backend.py b/hardware/mox/mox-rainbow-backend/files/animator_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..df13e80dbe0b741fa82463a4d415813f6d969ef6 --- /dev/null +++ b/hardware/mox/mox-rainbow-backend/files/animator_backend.py @@ -0,0 +1,25 @@ +import os +from euci import EUci + + +class Backend: + """Handler for all leds we can control.""" + + def __init__(self): + self._fd = os.open(f"/sys/class/leds/mox:red:activity/brightness", os.O_WRONLY) + self.uci = EUci() + + def update(self, ledid: int, red: int, green: int, blue: int) -> None: + """Update color of led on given index.""" + assert ledid == "activity" + brightness = self.uci.get("rainbow", "all", "brightness", dtype=int, default=255) + os.write(self._fd, f"{red * brightness / 255}".encode()) + + def apply(self) -> None: + """Apply previous LEDs state updates if that is required.""" + # We apply immediatelly so we do not need this. + + @staticmethod + def handled(ledid: int) -> bool: + """Informs animator if given led animation should be handled by it.""" + return True diff --git a/hardware/mox/mox-rainbow-backend/files/backend.sh b/hardware/mox/mox-rainbow-backend/files/backend.sh new file mode 100644 index 0000000000000000000000000000000000000000..baa79541e3ae9584372f210fd161b3e5e5db85fe --- /dev/null +++ b/hardware/mox/mox-rainbow-backend/files/backend.sh @@ -0,0 +1,63 @@ +# Turris Mox backend for generic rainbow script +LEDS="activity" + +SYSFS="/sys/class/leds/mox:red:activity" + +led2sysfs() { + local led="$1" + [ "$led" = "activity" ] || return + echo "$SYSFS" +} + +preapply() { + # TODO we can use pattern trigger to replace animator + /etc/init.d/rainbow-animator pause +} + +set_led() { + local led="$1" r="$2" g="$3" b="$4" mode="$5" + shift 5 + [ "$led" = "activity" ] || return + + if [ "$mode" = "auto" ]; then # override auto mode + mode="activity" + mode_args="heartbeat" + fi + local global_brightness + global_brightness="$(brightness)" + + local brightness=255 trigger="none" + case "$mode" in + disable) + brightness=0 + ;; + enable) + ;; + activity) + brightness=0 + trigger="activity" + ;; + animate) + # For animation we we rely on animator + ;; + *) + internal_error "Unsupported mode:" "$mode" + ;; + esac + brightness=$(((brightness * global_brightness * r) / (255 * 255))) + + # We have to disable trigger first to make sure that changes are correctly + # applied and not modified by this in the meantime. + echo "none" > "$SYSFS/trigger" + echo "$brightness" > "$SYSFS/brightness" + if [ "$trigger" = "activity" ]; then + apply_activity "$led" "$@" \ + || echo "Warning: activity setup failed for: $led" >&2 + else + echo "$trigger" > "$SYSFS/trigger" + fi +} + +postapply() { + /etc/init.d/rainbow-animator reload +} diff --git a/hardware/omnia/omnia-rainbow-backend/Makefile b/hardware/omnia/omnia-rainbow-backend/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..ed0c8da337409d9d93fbc9390a208bd175a25f25 --- /dev/null +++ b/hardware/omnia/omnia-rainbow-backend/Makefile @@ -0,0 +1,37 @@ +# +## Copyright (C) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) +# +## This is free software, licensed under the GNU General Public License v3. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=omnia-rainbow-backend +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=CZ.NIC +PKG_LICENSE:=GPL-3.0-or-later +PKG_LICENSE_FILES:=LICENSE + +include $(INCLUDE_DIR)/package.mk + +define Package/omnia-rainbow-backend + TITLE:=omnia-rainbow-backend + DEPENDS:=\ + @TARGET_mvebu_cortexa9_DEVICE_cznic_turris-omnia \ + +rainbow-button-sync \ + +rainbow-animator + PROVIDES:=rainbow-backend +endef + +Build/Compile:=: + +define Package/omnia-rainbow-backend/install + $(INSTALL_DIR) $(1)/usr/share + $(INSTALL_DATA) ./files/backend.sh $(1)/usr/libexec/rainbow/ + $(INSTALL_DATA) ./files/animator_backend.sh $(1)/usr/libexec/rainbow/ +endef + +$(eval $(call BuildPackage,omnia-rainbow-backend)) diff --git a/hardware/omnia/omnia-rainbow-backend/files/animator_backend.py b/hardware/omnia/omnia-rainbow-backend/files/animator_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..c866641f29ede11702f93e94a95ab134ec2d7312 --- /dev/null +++ b/hardware/omnia/omnia-rainbow-backend/files/animator_backend.py @@ -0,0 +1,24 @@ +import os + + +class Backend: + """Handler for all leds we can control.""" + + # All available leds in order on the box + LEDS = ["power", "lan0", "lan1", "lan2", "lan3", "lan4", "wan", "pci1", "pci2", "pci3", "user1", "user2"] + + def __init__(self): + self._fds = tuple(os.open(f"/sys/class/leds/omnia-led:{led}/color", os.O_WRONLY) for led in self.LEDS) + + def update(self, ledid: int, red: int, green: int, blue: int) -> None: + """Update color of led on given index.""" + os.write(self._fds[ledid], f"{red} {green} {blue}".encode()) + + def apply(self) -> None: + """Apply previous LEDs state updates if that is required.""" + # We apply immediatelly so we do not need this. + + @staticmethod + def handled(ledid: int) -> bool: + """Informs animator if given led animation should be handled by it.""" + return True # On Omnia all leds could be animated using animator diff --git a/hardware/omnia/omnia-rainbow-backend/files/backend.sh b/hardware/omnia/omnia-rainbow-backend/files/backend.sh new file mode 100644 index 0000000000000000000000000000000000000000..6e5fa8fd935048e48698c38d0b931d0d782e01a5 --- /dev/null +++ b/hardware/omnia/omnia-rainbow-backend/files/backend.sh @@ -0,0 +1,158 @@ +# Turris Omnia backend for generic rainbow script +LEDS="pwr lan0 lan1 lan2 lan3 lan4 wan pci1 pci2 pci3 usr1 usr2" + +SYSFS="/sys/devices/platform/soc/soc:internal-regs/f1011000.i2c/i2c-0/i2c-1/1-002b" + +led2sysfs() { + local led="$1" + case "$led" in # Transform our names of leds to the driver names + pwr) + led="power" + ;; + usr*) + led="user${led#usr}" + ;; + esac + echo "$SYSFS/leds/omnia-led:$led" +} + +led_defaults() { + local led="$1" + case "$led" in + pwr) + color_r=0 color_g=255 color_b=0 + ;; + lan*|pci*) + color_r=0 color_g=0 color_b=255 + ;; + wan) + color_r=0 color_g=255 color_b=255 + ;; + esac +} + +get_brightness() { + local cur + cur="$(cat "$SYSFS/global_brightness")" + echo $((cur * 255 / 100)) +} + +preapply() { + /etc/init.d/rainbow-animator pause + + local brightness_level + brightness_level="$(brightness)" + # The full range is only 0-100 but that is good enough so we don't adjust + # more. We just loose the precision on Omnia but who can see it anyway. + echo "$((brightness_level * 100 / 255))" > "$SYSFS/global_brightness" +} + +set_led() { + local led="$1" r="$2" g="$3" b="$4" mode="$5" + shift 5 + + if [ "$mode" = "auto" ]; then # override auto mode for some + case "$led" in + pwd) + mode="enable" + ;; + pci*) + local trigger + loadsrc pci + if trigger="$(pci_device_activity_detect "$led" "/sys/bus/pci/devices/0000:0${led#pci}:00.0")"; then + mode="activity" + set "$trigger" + else + mode="disable" + fi + ;; + usr*) + mode="disable" + ;; + esac + fi + + local brightness=255 autonomous=0 trigger="none" + case "$mode" in + auto) + autonomous=1 + ;; + disable) + brightness=0 + ;; + enable) + ;; + activity) + trigger="activity" + ;; + animate) + # For animation we we rely on animator + ;; + *) + internal_error "Unsupported mode:" "$mode" + ;; + esac + + local sysfs + sysfs="$(led2sysfs "$led")" + # We have to disable trigger first to make sure that changes are correctly + # applied and not modified by this in the meantime. + echo "none" > "$sysfs/trigger" + echo "$brightness" > "$sysfs/brightness" + echo "$r $g $b" > "$sysfs/color" + echo "$autonomous" > "$sysfs/autonomous" + if [ "$trigger" = "activity" ]; then + apply_activity "$led" "$@" \ + || echo "Warning: activity setup failed for: $led" >&2 + else + echo "$trigger" > "$sysfs/trigger" + fi +} + +postapply() { + /etc/init.d/rainbow-animator reload +} + + +boot_sequence() { + local sysfs + + for led in $LEDS; do + sysfs="$(led2sysfs "$led")" + echo "none" > "$sysfs/trigger" + echo "0" > "$sysfs/autonomous" + echo "255" > "$sysfs/brightness" + echo "0 255 0" > "$sysfs/color" + done + sleep 1 + + for led in $LEDS; do + sysfs="$(led2sysfs "$led")" + echo "0" > "$sysfs/autonomous" + echo "none" > "$sysfs/trigger" + echo "255" > "$sysfs/brightness" + echo "0 0 255" > "$sysfs/color" + done + sleep 1 + # Note: we apply in the end so we don't have to restore previous state +} + + +# Parse operations that were previously available with rainbow +compatibility() { + local operation="$1"; shift + SHIFTARGS=$# + case "$operation" in + binmask) + compat_binmask "$@" + ;; + intensity) + compat_intensity 100 "$@" + ;; + get) + compat_get 100 "$@" + ;; + esac + shift $(($# - SHIFTARGS)) + SHIFTARGS=$# +} diff --git a/hardware/omnia/omnia-rainbow/Makefile b/hardware/omnia/omnia-rainbow/Makefile index a8bf8468374597b22640091183a8f547a4fb2721..3eca71f1bf7008457d03b87b4b4db52281fe2124 100644 --- a/hardware/omnia/omnia-rainbow/Makefile +++ b/hardware/omnia/omnia-rainbow/Makefile @@ -1,5 +1,5 @@ # -## Copyright (C) 2016-2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) +## Copyright (C) 2016-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) # ## This is free software, licensed under the GNU General Public License v3. # See /LICENSE for more information. @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=omnia-rainbow PKG_VERSION:=3.1 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://gitlab.nic.cz/turris/rainbow_omnia.git @@ -25,45 +25,16 @@ include $(INCLUDE_DIR)/package.mk define Package/omnia-rainbow TITLE:=omnia-rainbow URL:=https://gitlab.nic.cz/turris/rainbow_omnia - PROVIDES:=turris-rainbow DEPENDS:=@TARGET_mvebu_cortexa9_DEVICE_cznic_turris-omnia endef -define Package/omnia-rainbow/conffiles -/etc/config/rainbow -endef - define Build/Compile $(MAKE_VARS) $(MAKE) -C $(PKG_BUILD_DIR) $(MAKE_FLAGS) endef define Package/omnia-rainbow/install $(INSTALL_DIR) $(1)/usr/bin/ - $(INSTALL_BIN) $(PKG_BUILD_DIR)/rainbow $(1)/usr/bin/ - $(INSTALL_DIR) $(1)/etc/init.d/ - $(INSTALL_BIN) ./files/rainbow.init $(1)/etc/init.d/rainbow - $(INSTALL_DIR) $(1)/etc/config/ - $(INSTALL_DATA) ./files/rainbow.config $(1)/etc/config/rainbow - $(INSTALL_DIR) $(1)/etc/cron.d - $(INSTALL_DATA) ./files/rainbow.cron $(1)/etc/cron.d/rainbow - $(INSTALL_BIN) ./files/rainbow_button_sync.sh $(1)/usr/bin/rainbow_button_sync.sh -endef - -define Package/omnia-rainbow/postinst -#!/bin/sh -[ -n "$$IPKG_INSTROOT" ] || { - /etc/init.d/rainbow enable - /etc/init.d/rainbow restart -} -endef - -define Package/omnia-rainbow/prerm -#!/bin/sh -[ -n "$$IPKG_INSTROOT" ] || { - /etc/init.d/rainbow stop - /etc/init.d/rainbow disable - rainbow all auto white -} + $(INSTALL_BIN) $(PKG_BUILD_DIR)/rainbow $(1)/usr/bin/omnia-rainbow endef $(eval $(call BuildPackage,omnia-rainbow)) diff --git a/hardware/omnia/omnia-rainbow/files/rainbow.config b/hardware/omnia/omnia-rainbow/files/rainbow.config deleted file mode 100644 index 88122fcf4170c36870b96d9f33fe60a900f12496..0000000000000000000000000000000000000000 --- a/hardware/omnia/omnia-rainbow/files/rainbow.config +++ /dev/null @@ -1,25 +0,0 @@ -# You can set all the leds at once. -config led 'all' - option color 'white' - option status 'auto' - -# Any other config will overwrite the value -#config led 'pwr' -# option color 'FF0066' -# option status 'auto' -# -#config led 'lan0' -# option color 'FFFF00' -# option status 'auto' -# -#config led 'wan' -# option color 'green' -# option status 'auto' -# -#config led 'pci1' -# option color 'blue' -# option status 'auto' -# -# Available LEDs: all, pwr, lan0, lan1, lan2, lan3, wan, pci1, pci2, pci3, usr1, usr2 -# Available color: white, black, red, green, blue, or use hex code of color like: FFFF00 -# For more information: rainbow --help diff --git a/hardware/omnia/omnia-rainbow/files/rainbow.cron b/hardware/omnia/omnia-rainbow/files/rainbow.cron deleted file mode 100644 index cb4d5923d07cf057c061c73d1333a832d091d61a..0000000000000000000000000000000000000000 --- a/hardware/omnia/omnia-rainbow/files/rainbow.cron +++ /dev/null @@ -1,2 +0,0 @@ -MAILTO="" -* * * * * root /usr/bin/rainbow_button_sync.sh diff --git a/hardware/omnia/omnia-rainbow/files/rainbow.init b/hardware/omnia/omnia-rainbow/files/rainbow.init deleted file mode 100644 index 1f1d48059b42a47accba71e76ab22d5a13214d4f..0000000000000000000000000000000000000000 --- a/hardware/omnia/omnia-rainbow/files/rainbow.init +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh /etc/rc.common - -# Start at the end, so the colors change after the boot is done. -START=99 -STOP=00 - -PID_FILE="/var/run/rainbow.pid" - -reload() { - # Set the colors based on user preferrences - uci show rainbow | sed -ne 's/rainbow\.\([^.]*\)=led/\1/p' | sort | while read SECTION ; do - STATUS=$(uci get rainbow.$SECTION.status) - COLOR=$(uci get rainbow.$SECTION.color) - rainbow $SECTION $STATUS $COLOR - done -} - -restart() { - rainbow all enable green - sleep 1 - reload -} - -start() { - ( - # Signal the boot is finished - rainbow all enable blue - sleep 1 - rainbow all enable green - sleep 1 - reload - # Resume last intensity settings - rainbow intensity $(cat /etc/rainbow.magic) - ) & -} - -stop() { - # Reset to the default, so a reboot can be recognized. - rainbow all enable white -} diff --git a/hardware/omnia/omnia-rainbow/files/rainbow_button_sync.sh b/hardware/omnia/omnia-rainbow/files/rainbow_button_sync.sh deleted file mode 100755 index 5f30d9f3cb61881baf6388a5e8ac96e0338e2305..0000000000000000000000000000000000000000 --- a/hardware/omnia/omnia-rainbow/files/rainbow_button_sync.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -MAGIC_FILE="/etc/rainbow.magic" - -# Get actual intensity -ACT_INTENSITY=$(rainbow get intensity) - -# Get value from magic file -[ -f "$MAGIC_FILE" ] || echo 0 > "$MAGIC_FILE" -LAST_VALUE=$(cat "$MAGIC_FILE") - -# Compare them and if last value is different from stored one update it -if [ "$ACT_INTENSITY" != "$LAST_VALUE" ]; then - echo $ACT_INTENSITY > "$MAGIC_FILE" -fi diff --git a/hardware/rainbow/Makefile b/hardware/rainbow/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..e11e900b179c7904e70a56e8a718d6e3af53ffe2 --- /dev/null +++ b/hardware/rainbow/Makefile @@ -0,0 +1,120 @@ +# +## Copyright (C) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) +# +## This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# # +# +include $(TOPDIR)/rules.mk + +PKG_NAME:=rainbow +PKG_VERSION:=1.0.0 + +PKG_MAINTAINER:=CZ.NIC + +include $(INCLUDE_DIR)/package.mk + +define Package/Common + TITLE:=rainbow + URL:=https://gitlab.nic.cz/turris/rainbow_omnia + DEPENDS:=@(TARGET_mvebu_cortexa9_DEVICE_cznic_turris-omnia||TARGET_mpc85xx_p2020_DEVICE_turris1x||TARGET_mvebu_cortexa53_DEVICE_cznic-mox) \ +endef + +define Package/rainbow + $(call Package/Common) + DEPENDS+= +rainbow-backend + PROVIDES:=turris-rainbow +endef + +define Package/rainbow-button-sync + $(call Package/Common) + TITLE+=-button-sync +endef + +define Package/rainbow-animator + $(call Package/Common) + TITLE+=-animator + DEPENDS+= +python3-base +endef + +define Package/rainbow/description + Leds control on Turris Omnia, Mox and 1.x routers. +endef + +define Package/rainbow-button-sync/description + Button synchronization extension for Turris Rainbow. This is used to synchronize system configuration with hardware button. +endef + +define Package/rainbow-animator/description + Animation extension for Turris Rainbow. This is used to animate leds in case of missing or unusable kernel support. +endef + +define Package/rainbow/install + $(INSTALL_DIR) $(1)/usr/bin + ln -sf /usr/libexec/rainbow/rainbow.sh $(1)/usr/bin/rainbow + $(INSTALL_DIR) $(1)/etc/init.d/ + $(INSTALL_BIN) ./files/rainbow.init $(1)/etc/init.d/rainbow + $(INSTALL_DIR) $(1)/etc/config/ + touch $(1)/etc/config/rainbow + + $(INSTALL_DIR) $(1)/usr/libexec/rainbow + $(INSTALL_BIN) ./files/rainbow.sh $(1)/usr/libexec/rainbow/ + $(INSTALL_DATA) \ + ./files/animation.sh \ + ./files/brightness.sh \ + ./files/color.sh \ + ./files/compat.sh \ + ./files/led.sh \ + ./files/led_activity.sh \ + ./files/led_animate.sh \ + ./files/ledid.sh \ + ./files/pci.sh \ + ./files/reset.sh \ + ./files/state.sh \ + ./files/uci.sh \ + ./files/utils.sh \ + $(1)/usr/libexec/rainbow/ +endef + +define Package/rainbow-button-sync/install + $(INSTALL_DIR) $(1)/etc/init.d/ + $(INSTALL_BIN) ./files/rainbow-button-sync.init $(1)/etc/init.d/rainbow-button-sync + + $(INSTALL_DIR) $(1)/usr/libexec/rainbow + $(INSTALL_BIN) ./files/button_sync.sh $(1)/usr/libexec/rainbow/ +endef + +define Package/rainbow-animator/install + $(INSTALL_DIR) $(1)/etc/init.d/ + $(INSTALL_BIN) ./files/rainbow-animator.init $(1)/etc/init.d/rainbow-animator + + $(INSTALL_DIR) $(1)/usr/libexec/rainbow + $(INSTALL_BIN) ./files/animator.py $(1)/usr/libexec/rainbow/ +endef + +define Package/rainbow/conffiles +/etc/config/rainbow +endef + +Build/Compile:=: + +define Package/rainbow/postinst +#!/bin/sh +[ -n "$$IPKG_INSTROOT" ] || { + /etc/init.d/rainbow enable + /etc/init.d/rainbow restart +} +endef + +define Package/rainbow/prerm +#!/bin/sh +[ -n "$$IPKG_INSTROOT" ] || { + /etc/init.d/rainbow stop + /etc/init.d/rainbow disable + rainbow all auto white +} +endef + +$(eval $(call BuildPackage,rainbow)) +$(eval $(call BuildPackage,rainbow-button-sync)) +$(eval $(call BuildPackage,rainbow-animator)) diff --git a/hardware/rainbow/files/README.md b/hardware/rainbow/files/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f896dc9dd9dc3dea8f6c4b28e54f87ec7ee149a6 --- /dev/null +++ b/hardware/rainbow/files/README.md @@ -0,0 +1,134 @@ +# Rainbow + +Rainbow is RGB LEDs manipulation script. It is intended for runtime changes of +color and brightness of LEDs. The permanent configuration that is used as a +bases for runtime changes is stored in UCI (it has to be used directly to modify +it). + + +## UCI Configuration + +The primary UCI config of Rainbow is `rainbow`. At the same time this is not the +only config Rainbow honors and uses. There is also `system`. The reason for this +is because OpenWrt's native LEDs handling already supports some of the features +Rainbow implements and having configuration on multiple places results in +conflicts. Rainbow tries to resolve them by almost always preferring `system` +configuration. + +The `rainbow` config is expected to contain sections of type `led` where section +name is LED name or `all` for all LEDs. The available led names can be listed +using `rainbow -l`. + +The `rainbow.LED` (where `LED` is valid name of LED) sections can contain +options: + +* `color`: default color of LED (the default depends on specific LED). See + `rainbow all -h` for possible values. Note that inheritence is not supported + in UCI configuration as there is nowhere to iherit values from. +* `mode`: default mode of LED. Continue reading for all available modes (the + default is `auto`). + +The section `all` serves as global defaults for other LEDs. It has also one +additional option `brightness`. It is an integer between 0 and 255 specifying +global LEDs brightness. + +The `mode` option can have the following values: + +* `auto`: the default behavior for given LED. +* `disable`: disable LED, so it simply is not used. +* `enable`: enable LED, so it always shines when router is powered on. +* `activity`: the LED should blink with specific system events (see following + section for explanation and configuration). This is not available on all + platforms as it depends on Kernel support! +* `animate`: performs specified animation (see the following section for + explanation and configuration). +* `ignore`: instruct Rainbow that this LED should be ignored by it (not + touched). + +### Activity + +This mode allows LED to be controlled by events happening in the system (Kernel +events). This configuration is mostly possible in UCI config `system` and thus +we allow use that as well. When trigger is configured in both configs the +`sytem` is used. + +The activity is not available on all platforms due to implementation. The +notable omission are Turris 1.x routers. + +The supported activity triggers (UCI config `trigger` in appropriate LED section +in `rainbow` config): + +* `activity`: Flash led with system activity (faster blinking for higher + activity). This is default if no `trigger` option is specified. +* `activity-inverted`: Blink led with system activity (same as `activity` but in + default it LED is enabled). +* `heartbeat`: Variant on `activity` that blinks twice instead of once. +* `heartbeat-inverted`: Variant on `activity-inverted` for `heartbeat`. +* `disk-activity`: Blinks with disk activity (any disks activity is considered). +* `usbport`: Blinks with activity on any USB port. +* `netdev-NETDEV`: Blink with activity on specific network device where `NETDEV` + is the name of that device. +* `mmcX`: Flashes when writing or reading to and from specific MMC device where + `X` is index of that device. +* `phyX`: Blinks with activity on wireless interface where `X`is index of that + interface. +* `ataX`: Blink when writing or reading to and from specific ATA device where + `X` is index of that device. +* `netfilter-ID`: Flashes when firewall rule with specified `ID` is triggered. + +The full list of available activities is available by invoking: +`rainbow all activity -l`. + +### Animate + +This allows specification of animation pattern. The simplest variant is periodic +enable and disable of LED but more complex patterns can be specified as well. + +The animation is specified as set of color and time in nanoseconds pairs. The +color changes from one color to the next one in duration of specified time. + +In configuration animation is specified as a list of those color and time in +nanoseconds pairs. For example the following UCI list gradually dims and +light ups green color on power led: +`rainbow.all.animation='green 1000' 'black 1000'` + + +## Hardware integration + +This section describes how to implement hardware integration for rainbow and +what that actually means. This is here as developer documentation not expected +by end-users to read this. + +The integration has to be done by implementing shell file `backend.sh` and +installed in the same directory as the rest of the rainbow. This directory in +repository contains also a commented example backend file you can use to +understand what you have to implement to get Rainbow working on a new platform. + +### Button sync + +Some of the boards have hardware button that allows limited LED brightness +control. We have to sync the setting to configuration so the usage of button is +not wiped after reboot or any rainbow call. This is provided by +`rainbow-button-sync` service and shell script `button_sync.sh`. The +`button_sync.sh` uses the same backend file as `rainbow.sh` and needs only one +additional function to be present there `get_brightness`. The configuration is +saved to the UCI config `rainbow`. + +### Animator + +The animations are designed to work well with Kernel LED pattern trigger. The +issue is that not every board has kernel driver and not only that but also not +every kernel driver allows us to control color using the trigger. To allow +animations even on such platforms we have `rainbow-animator` service that runs +`animator.py`. To make this works you have to implement `animator_backend.py` +which is simplified backend implemented in Python. You also have to pause +animator in `preapply` function in `backend.sh` with +`/etc/init.d/rainbow-animator pause` and reload it in `postapply` function with +`/etc/init.d/rainbow-animator reload`. The example animator backend file with +commends is provided in this repository. + +The animator can be configured to limit number of updates which is highly +desirable to not load CPU with LED animations. In default number of updates in +seconds is set to 15. You can use UCI `rainbow.animation.ups` to set different +value if you want tweak it. + diff --git a/hardware/rainbow/files/animation.sh b/hardware/rainbow/files/animation.sh new file mode 100644 index 0000000000000000000000000000000000000000..f23b1f46f108f0d4e6ba54af67bd09b1eed19ef9 --- /dev/null +++ b/hardware/rainbow/files/animation.sh @@ -0,0 +1,79 @@ +loadsrc color + +# Initialize animation variables +# Yes yes this is not nice as it uses global variables but iterative addition is +# the easies API considering the usage in uci.sh that I could come up with. +animation_init() { + animation="" + animation_color_r="?" + animation_color_g="?" + animation_color_b="?" +} + +animation_add() { + local color="$1" + local time="$2" + local color_r="$animation_color_r" + local color_g="$animation_color_g" + local color_b="$animation_color_b" + + parse_color "$1" inherit || return + echo "$time" | grep -qE "^[0-9]+$" || return + + animation="${animation}${animation:+ }$color_r $color_g $color_b $time" + animation_color_r="$color_r" + animation_color_g="$color_g" + animation_color_b="$color_b" +} + +animation_finish() { + if [ -n "$animation" ]; then + # The magic in the awk is that question mark is used only for values + # from initial steps and never after if there was some other value. This + # means that we can replace any occurrences of it by values from last + # state. The exception is when value was never specified, then the + # default that is inheritance is used. + animation="$(echo "$animation" | awk -F'\t' '{ + OFS="\t" + for (i = 0; i < 4; i++) { + c[i] = $(NF - 3 + i) + if (c[i] == "?") + c[i] = "-" + } + for (i = 1; i <= NF; i++) + if ($i == "?") + $i = c[(i - 1) % 4] + print + }')" + fi +} + +# Load animation sequence and format it to internal representation. +# Arguments are expected to be pairs of color and time. The result is saved to +# the animation variable. +# The number of consumed arguments is signaled by setting SHIFTARGS to the $#. +parse_animation() { + # Explanation about magic here.. + # The animation is sequence of values that repeats for inifinity. This means + # that color in next step modifies the previous one and the first one + # modifies the last one. As an example let's take sequence: + # R255 100 R0 100 G255 100 G0 100 + # In this sequence the initial two states do not specify green color but + # from sequence it is clear that it should be zero. The same applies for the + # red color in last two states. + # To assemble complete sequence we have to always modify previously + # specified value. This is achieved simply by updating previous color. The + # issue is that for initial steps the previous values are the last ones so + # we have to read all steps before we can propagate them back. + # The solution here is to use temporally value for any value we are not sure + # about and fill it later on when we read all arguments. + local animation_color_r animation_color_g animation_color_b + animation_init + # We always parse two arguments at once (color and time) + while [ $# -ge 2 ]; do + animation_add "$1" "$2" || break + shift 2 + done + animation_finish + SHIFTARGS=$# +} diff --git a/hardware/rainbow/files/animation.test.sh b/hardware/rainbow/files/animation.test.sh new file mode 100755 index 0000000000000000000000000000000000000000..6d6053ca149bed652d1edd9d9995c74d9f4dea01 --- /dev/null +++ b/hardware/rainbow/files/animation.test.sh @@ -0,0 +1,37 @@ +#!/bin/ash +set -eu +loadsrc() { + . "${0%/*}/$1.sh" +} +loadsrc animation + +fail() { + echo "$@" >&2 + exit 1 +} + +check() { + local seq="$1" + local residue="$2" + shift 2 + parse_animation "$@" || fail "Parse failed for: $*" + [ "$animation" = "$seq" ] || fail "Invalid result '$animation' != '$seq' for: $*" + [ "$SHIFTARGS" = "$residue" ] || fail "Invalid number of residue for: $*" +} + + +check "" 0 +check \ + "0 0 0 1000 255 0 0 1000" \ + 0 \ + black 1000 red 1000 +check \ + "255 0 - 1000 0 0 - 1000 0 255 - 1000 0 0 - 1000" \ + 0 \ + R255 1000 R0 1000 G255 1000 G0 1000 + +check "" 2 something else +check \ + "0 0 0 1000 255 0 0 1000" \ + 2 \ + black 1000 red 1000 something else diff --git a/hardware/rainbow/files/animator.py b/hardware/rainbow/files/animator.py new file mode 100755 index 0000000000000000000000000000000000000000..85862d62c117fa25cce0ff9cd071d60852db6807 --- /dev/null +++ b/hardware/rainbow/files/animator.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +import argparse +import fcntl +import importlib.util +import math +import os +import pathlib +import signal +import sys +import time +import typing + +DEFAULT_UPS = 15 # Configures how many updates we try to reach per second + +COLORS = ("r", "g", "b") + +# Load backend +backend_spec = importlib.util.spec_from_file_location("backend", pathlib.Path(__file__).parent / "animator_backend.py") +assert backend_spec +backend_module = importlib.util.module_from_spec(backend_spec) +backend_spec.loader.exec_module(backend_module) +backend = backend_module.Backend() + +rainbowdir = pathlib.Path("/var/run/rainbow") + +is_paused = False +configuration: dict[int, list[dict[str, int]]] = {} +state: dict[int, dict] = {} + + +def report(*args, **kwargs): + """The replacement for simple print that immediatelly flushes output. The issue here is with pipe setup in procd + that does not buffer simply to the end of line. The effect is that simple print is not immediatelly displayed in + logs without this flush. + """ + print(*args, **kwargs) + sys.stdout.flush() + + +def pause(): + """Pause animation. To resume the reload has to be called.""" + global is_paused + is_paused = True + report("Paused") + + +def _field2value(field: str) -> typing.Optional[int]: + """Converts field to integer or None in case of '-'.""" + if field == "-": + return None + return int(field) + + +def _base_color(base_color, i, color, new: str) -> typing.Optional[int]: + new = _field2value(new) + if new is None: + if i in base_color: + return base_color[i][color] + return new + + +def reload(): + """Load latest rainbow configuration and resume animation.""" + report("Reloading...") + configuration.clear() + try: + dirfd = os.open(rainbowdir, os.O_RDONLY | os.O_DIRECTORY) + except FileNotFoundError: + return # No configuration so just simply skip loading + fcntl.flock(dirfd, fcntl.LOCK_EX) + + base_color: dict[int, dict[str, typing.Optional[int]]] = {} + files = [pth for pth in rainbowdir.iterdir() if pth.is_file and not pth.name.startswith(".")] + files.sort() + for filepath in files: + with filepath.open() as file: + i = 0 + for line in file: + fields = line.rstrip().split("\t") + base_color[i] = {COLORS[y]: _base_color(base_color, i, COLORS[y], fields[y]) for y in range(3)} + if fields[3] == "animate" and backend.handled(i): + frames = [] + offset = 4 + while len(fields) - offset >= 4: + frame = {COLORS[y]: _field2value(fields[offset + y]) for y in range(3)} + frame["ns"] = int(fields[offset + 3]) * 1000000 + frames.append(frame) + offset += 4 + if frames: + configuration[i] = frames + elif i in configuration and fields[3] != "-": + # Some different configuration so make sure that we ignore the previous one + del configuration[i] + i += 1 + # Note: We have to collect base color from all levels first before we assign it as some higher level might change it + # without updating mode. + for ledid, points in configuration.items(): # Fill in base color + for point in points: + for color in COLORS: + point[color] = (base_color[ledid][color] or 0) if point[color] is None else point[color] + + state_keys = set(state.keys()) + configuration_keys = set(configuration.keys()) + for ledid in state_keys - configuration_keys: # Remove old states + del state[ledid] + for ledid in configuration_keys - state_keys: # Initialize new states + state[ledid] = { + "index": 0, + "start": time.monotonic_ns(), + } + + os.close(dirfd) # unlocks directory + global is_paused + is_paused = False + + +def _interpolate(a, b, point): + return int(a + ((b - a) * point)) + + +def interpolate(a, b, timeoff): + """Interpolates two colors to the given point between them for given time offset.""" + point = timeoff / a["ns"] + red = _interpolate(a["r"], b["r"], point) + green = _interpolate(a["g"], b["g"], point) + blue = _interpolate(a["b"], b["b"], point) + return red, green, blue + + +def update(): + """Animation function.""" + for ledid, points in configuration.items(): + curi = state[ledid]["index"] + nexti = curi + 1 + if len(points) <= nexti: + nexti = 0 + timeoff = time.monotonic_ns() - state[ledid]["start"] + while timeoff > points[curi]["ns"]: + timeoff -= points[curi]["ns"] + curi = nexti + nexti += 1 + if len(points) <= nexti: + nexti = 0 + red, green, blue = interpolate(points[curi], points[nexti], timeoff) + backend.update(ledid, red, green, blue) + backend.apply() + + +def parse_arguments(): + """Parse script arguments""" + parser = argparse.ArgumentParser( + description="Rainbow Animator. Helper for platforms that do not correctly support pattern trigger on LEDs." + ) + parser.add_argument("--ups", "-u", default=15, help="Set number of updates per-second.") + return parser.parse_args() + + +def main(): + args = parse_arguments() + reload() # Load the first configuration + signal.signal(signal.SIGUSR1, lambda s, f: pause()) + signal.signal(signal.SIGUSR2, lambda s, f: reload()) + nano = math.pow(10, 9) + frame = nano / args.ups + while True: + if not is_paused and configuration: + stime = time.monotonic_ns() + signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGUSR1, signal.SIGUSR2]) + update() + signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGUSR1, signal.SIGUSR2]) + rtime = time.monotonic_ns() - stime + if rtime < frame: + time.sleep((frame - rtime) / nano) # sleep for rest of the frame + else: + sig = signal.sigwait([signal.SIGUSR1, signal.SIGUSR2]) + if sig == signal.SIGUSR1: + pause() + elif sig == signal.SIGUSR2: + reload() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/hardware/rainbow/files/animator_backend.example.py b/hardware/rainbow/files/animator_backend.example.py new file mode 100644 index 0000000000000000000000000000000000000000..99a930a1b0cc26932079ad65ab75e93cee30229a --- /dev/null +++ b/hardware/rainbow/files/animator_backend.example.py @@ -0,0 +1,14 @@ +class Backend: + """Handler for all leds we can control.""" + + def update(self, ledid: int, red: int, green: int, blue: int) -> None: + """Update color of led on given index.""" + # TODO + + def apply(self) -> None: + """Apply previous LEDs state updates if that is required.""" + # TODO + + def handled(self, ledid: int) -> bool: + """Informs animator if led animation for led on given index should be handled by it.""" + return False # TODO diff --git a/hardware/rainbow/files/backend.example.sh b/hardware/rainbow/files/backend.example.sh new file mode 100644 index 0000000000000000000000000000000000000000..4eb9dcd2ab63d0583b5687bf708de92da02ba687 --- /dev/null +++ b/hardware/rainbow/files/backend.example.sh @@ -0,0 +1,82 @@ +# Backend for generic rainbow script EXAMPLE + +# This variable has to be always defined with full list of LED names. It is +# prefered to use self-describing but short names for those LEDs. It is prefered +# that those names match with labeling on the box. +LEDS="pwr lan0 lan1 lan2 wan" + +# This function is required only if leds are controlled by kernel driver and +# kernel managed triggers are available. When this function is defined and +# successfully provides path to sysfs for specified LED then it unlocks +# activity triggers for it. +led2sysfs() { + local led="$1" # one of leds from LEDS variable + false +} + +# Allows modification of defaults for specific leds (it is called with 'all' as +# well). +# It is called in environment where following variables are defined and +# available for modification: +# color_r: red color +# color_g: green color +# color_b: blue color +# mode: rainbow mode for the LED +# mode_args: additional arguments for the mode +led_defaults() { + local led="$1" + false +} + +# This function is optional and is required only for button_sync.sh. +# It is really needed only if there is a hardware way to modify brightness. +# It is expected to echo the current brightness level as integer between 0 and +# 255. +get_brightness() { + false +} + +# The optional function that is called before application of state using +# set_led. This should be used to prepare environment as well as to pause +# animator for example. +preapply() { + false +} + +# This function has to be defined in every backend. It is a core component of +# rainbow functionality. This applies configuration to one specific LED. +# It gets LED name, color as RGB components, mode and mode's arguments. +set_led() { + local led="$1" r="$2" g="$3" b="$4" mode="$5" + shift 5 + false +} + +# The optional function that is called after set_led was called for every led +# and thus state of them is set. This should be used to do cleanup and to resume +# animator for example. +postapply() { + false +} + + +# Perform boot sequence specific for this device. +# This function is only optional and does not have to be defined if there is no +# boot sequence. +boot_sequence() { + false + # Note: The current state is always applied in the end in when this function + # is called so we don't have to restore previous state here. +} + + +# Parse operations that were previously available with rainbow. +# This adds option to correctly replace previous rainbow command without being +# stuck with its argument parsing. +# This is for backward compatibility and you want to remove this function for +# new platforms. +compatibility() { + local operation="$1"; shift # Operation user specified and we try to parse here + false + SHIFTARGS=$# # This is required to inform called how many arguments we consumed. +} diff --git a/hardware/rainbow/files/brightness.sh b/hardware/rainbow/files/brightness.sh new file mode 100644 index 0000000000000000000000000000000000000000..edc6c6870a89bba88817fbbd932f2963c457c29d --- /dev/null +++ b/hardware/rainbow/files/brightness.sh @@ -0,0 +1,73 @@ +loadsrc uci + +brightness_usage() { + echo "Usage: $0 brightness [OPTION].. " >&2 +} +brightness_help() { + brightness_usage + # Note: We use 0-8 as that is the number of levels Omnia can specify using + # button. The Turris 1.x has only 7 levels but we can use 7 and 8 as the + # same. The Mox has no levels thus we are free to choose what ever we want. + cat >&2 <<-EOF + Set maximum brightness (the global modifier for all controlled leds). + The brightness has to be specified as number between 0 and 8 (or 255 + if -p is used). + + Options: + -q Query for current setting instead of setting brightness + -p Higher precission for brightness + -h Print this help text and exit + EOF +} + +op_brightness() { + local query="f" + local precise="n" + while getopts "qh" opt; do + case "$opt" in + q) + query="y" + ;; + p) + precise="y" + ;; + h) + brightness_help + exit 0 + ;; + *) + brightness_usage + exit 2 + ;; + esac + done + if [ "$query" = "y" ]; then + brightness fetch + return $# + fi + + shift $((OPTIND - 1)) + [ $# -gt 0 ] || { + brightness_usage + exit 2 + } + brightness="$1" + if [ "$precise" = "y" ]; then + if [ "$brightness" -lt 0 ] || [ "$brightness" -gt 255 ]; then + echo "The value has to be a number from 0 to 255!" >&2 + brightness_usage + exit 2 + fi + else + if [ "$brightness" -lt 0 ] || [ "$brightness" -gt 8 ]; then + echo "The value has to be a number from 0 to 8!" >&2 + brightness_usage + exit 2 + fi + brightness=$((brightness * 32)) + fi + update_brightness "$brightness" + apply_needed="y" + + SHIFTARGS=$# +} diff --git a/hardware/rainbow/files/button_sync.sh b/hardware/rainbow/files/button_sync.sh new file mode 100755 index 0000000000000000000000000000000000000000..b9684e5a42491166699fcbf8f96d1d35527d88c9 --- /dev/null +++ b/hardware/rainbow/files/button_sync.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -eu +. "$(dirname "$(readlink -f "$0")")/utils.sh" + +loadsrc backend +loadsrc state + +type get_brightness >/dev/null \ + || fail "Button sync is not supported on this board!" + + +trap 'exit 0' INT QUIT TERM +while true; do + # TODO we might race here with rainbow configuration (uci changes but change + # is not yet propagated). It is very unlikelly but probably possible. + current_brightness="$(get_brightness)" + if [ "$(brightness fetch)" != "$current_brightness" ]; then + echo "Brightness update using button to: $current_brightness" + update_brightness "$current_brightness" + fi + sleep 2 +done diff --git a/hardware/rainbow/files/color.sh b/hardware/rainbow/files/color.sh new file mode 100644 index 0000000000000000000000000000000000000000..5bc7ece3a2ed478bb586314df627f7cb94ebc22e --- /dev/null +++ b/hardware/rainbow/files/color.sh @@ -0,0 +1,115 @@ +# Canonize color to RGB 0-255 format. +# Sets three variables: color_r, color_g, color_b +parse_color() { + local r="" g="" b="" + _parse_color "$@" || return + [ -z "$r" ] || color_r="$r" + [ -z "$g" ] || color_g="$g" + [ -z "$b" ] || color_b="$b" +} + +_parse_value() { + local value="$1" + local mode="$2" + [ "${value#x}" == "$value" ] \ + || value="$((16#${value#x}))" + + if [ "$mode" = "inherit" ] && [ "$value" = "-" ]; then + true + elif [ -n "$value" ]; then + echo "$value" | grep -qE "^[0-9]*$" || return + [ "$value" -ge 0 ] || return + [ "$value" -le 255 ] || return + fi + + echo "$value" +} + +_parse_color() { + local value="$1" + local mode="${2:-}" + + if [ -z "$value" ]; then + return # Empty value means no modification + fi + + case "$value" in + -) + if [ "$mode" = "inherit" ]; then + r="-" g="-" b="-" + return + fi + ;; + red) + r=255 g=0 b=0 + return + ;; + green) + r=0 g=255 b=0 + return + ;; + blue) + r=0 g=0 b=255 + return + ;; + yellow) + r=255 g=255 b=0 + return + ;; + cyan) + r=0 g=255 b=255 + return + ;; + magenta) + r=255 g=0 b=255 + return + ;; + white) + r=255 g=255 b=255 + return + ;; + black) + r=0 g=0 b=0 + return + ;; + esac + + if echo "$value" | grep -qE "^[0-9-]*,[0-9-]*,[0-9-]*$"; then + matched="y" + r="$(_parse_value "${value%%,*}" "$mode")" || return + local inter="${value%,*}" + g="$(_parse_value "${inter#*,}" "$mode")" || return + b="$(_parse_value "${value##*,}" "$mode")" || return + return + fi + + if echo "$value" | grep -qE "^[RGB]([0-9]+|x[0-9a-fA-F]+)(,[RGB]([0-9]+|x[0-9a-fA-F]+))*$"; then + matched="y" + while read -r spec; do + case "$spec" in + R*) + r="$(_parse_value "${spec#R}" "$mode")" || return + ;; + G*) + g="$(_parse_value "${spec#G}" "$mode")" || return + ;; + B*) + b="$(_parse_value "${spec#B}" "$mode")" || return + ;; + esac + done <<-EOF + $(echo "$value" | tr ',' '\n') + EOF + return + fi + + # Hex format for backward compatibility + if echo "$value" | grep -qE "^[0-9a-fA-F]{6}$"; then + r="$(_parse_value "x${1:0:2}" "")" || return + g="$(_parse_value "x${1:2:2}" "")" || return + b="$(_parse_value "x${1:4:2}" "")" || return + return + fi + + return 1 +} diff --git a/hardware/rainbow/files/color.test.sh b/hardware/rainbow/files/color.test.sh new file mode 100755 index 0000000000000000000000000000000000000000..cb3de14d1c7a231cb17fcdf867071c610b889403 --- /dev/null +++ b/hardware/rainbow/files/color.test.sh @@ -0,0 +1,75 @@ +#!/bin/ash +set -eu +. "${0%/*}/color.sh" + +fail() { + echo "$@" >&2 + exit 1 +} + +check() { + local repre="$1" + local r="$2" g="$3" b="$4" mode="${5:-}" + local color_r="?" color_g="?" color_b="?" + parse_color "$repre" "$mode" || fail "Parse failed for: $repre" + [ "$color_r" = "$r" ] || fail "Red color not matches $color_r != $r for: $repre" + [ "$color_g" = "$g" ] || fail "Green color not matches $color_g != $g for: $repre" + [ "$color_b" = "$b" ] || fail "Blue color not matches $color_b != $b for: $repre" +} + +check_fail() { + local repre="$1" mode="${2:-}" + local color_r="?" color_g="?" color_b="?" + ! parse_color "$repre" "$mode" || fail "Parse did not fail for: $repre" + [ "$color_r" = "?" ] || fail "Red color modified" + [ "$color_g" = "?" ] || fail "Green color modified" + [ "$color_b" = "?" ] || fail "Blue color modified" +} + +check "0,0,0" 0 0 0 +check "255,0,255" 255 0 255 +check "42,0,1" 42 0 1 +check "0,0,255" 0 0 255 +check "255,0,0" 255 0 0 +check ",0,0" "?" 0 0 +check "128,," 128 '?' '?' +check ",," '?' '?' '?' +check_fail "532,0,0" +check_fail "0,342,123" +check_fail "42,255,256" + +check "R0,G0,B0" 0 0 0 +check "R255,B255" 255 '?' 255 +check "R42,B1" 42 '?' 1 +check "B255" '?' '?' 255 +check "G255" '?' 255 '?' +check "" '?' '?' '?' + +check "Rx0,Gx0,Bx0" 0 0 0 +check "RxFF,BxFF" 255 '?' 255 +check "Rx2A,Bx1" 42 '?' 1 +check "Bxff" '?' '?' 255 +check "GxFf" '?' 255 '?' + +check "red" 255 0 0 +check "green" 0 255 0 +check "blue" 0 0 255 +check "yellow" 255 255 0 +check "cyan" 0 255 255 +check "magenta" 255 0 255 +check "white" 255 255 255 +check "black" 0 0 0 + +check "-" "-" "-" "-" inherit +check "-,255,-" "-" "255" "-" inherit +check "123,-,-" "123" "-" "-" inherit + +check_fail "ABCDF" +check_fail "ABCDF01" + + +check "000000" 0 0 0 +check "FF00FF" 255 0 255 +check "2A0001" 42 0 1 +check "0000ff" 0 0 255 +check "fF0000" 255 0 0 diff --git a/hardware/rainbow/files/compat.sh b/hardware/rainbow/files/compat.sh new file mode 100644 index 0000000000000000000000000000000000000000..22fc9801a6604fc956b34f543ff9ef64da3565ab --- /dev/null +++ b/hardware/rainbow/files/compat.sh @@ -0,0 +1,64 @@ +# These are common arguments that were previously provided by rainbow but are +# now obsolete. They are implemented separately so backend can choose which it +# has to support (depending on rainbow introduction to the platform). + +compat_binmask() { + if [ $# -eq 0 ]; then + echo "Invalid binmask usage. Missing number." + exit 2 + fi + + _compat_binmask_modify() { + [ "$1" != "$led" ] \ + || mode="$2" + } + + local binmask="$1" + shift + [ "${binmask#0x}" == "$binmask" ] \ + || binmask="$((16#${binmask#0x}))" + + local mask=1 + for led in $LEDS; do + mask=$((mask << 1)) + done + + for led in $LEDS; do + mask=$((mask >> 1)) + local mode="disable" + [ "$((binmask & mask))" -eq 0 ] \ + || mode="enable" + call_for_leds_config_level "$priority" "$slot" _compat_binmask_modify \ + "$led" "$mode" + done + apply_needed="y" + SHIFTARGS=$# +} + + +compat_intensity() { + local max="$1"; shift + if [ $# -gt 0 ]; then + loadsrc uci + update_brightness "$(($1 * 255 / max))" + apply_needed="y" + shift + else + echo "Invalid intensity usage. Missing intensity value." >&2 + exit 2 + fi + SHIFTARGS=$# +} + + +compat_get() { + local max="$1"; shift + if [ "${1:-}" = "intensity" ]; then + echo "$(($2 * max / 255))" + shift 2 + else + echo "Invalid get usage. Missing 'intensity' argument." >&2 + exit 2 + fi + SHIFTARGS=$# +} diff --git a/hardware/rainbow/files/led.sh b/hardware/rainbow/files/led.sh new file mode 100644 index 0000000000000000000000000000000000000000..5dacccdb5120cb6ca1a6cf5831aa06077d704949 --- /dev/null +++ b/hardware/rainbow/files/led.sh @@ -0,0 +1,154 @@ +loadsrc color +loadsrc ledid + +led_usage() { + echo "Usage: $0 $1 [OPTION].. [COLOR] [MODE]" >&2 +} +led_help() { + led_usage "$1" + cat >&2 <<-EOF + Configure '$1'. When no color or mode is given it prints current + configuration. The output is in tab separated columns with RGB color in + initial three columns, followed by mode and its arguments. + + Color: + Color can be specified by few different ways. You can simply use + specific color name (although supported set is small). + Another option is to specify RGB compounds. They are integer between 0 + and 255. They can be specified either in decimal format or in + hexadecimal if you prefix it with x (thus xFF is 255). Compounds are + expected to be joined by commad (such as 255,0,0 for red). You can + also specify compounds out of order by prefixing them with leter R for + red, G for green and B for blue (such as G0,B0,R255 for red). Not + every color compound has to be defined. They are filled with previous + value if you left some out (such as ,255, or G255) or with default + '-'. The '-' is the special value that specifies inheritence from + lower priority configuration. You can use it as replacement for any + color compound or allone to set inheritence. + Mode: + auto + Operate led status as designed (that is for example ethernet activity for lan leds) + disable + Disable led (keep it off) + enable + Enable led (keep it on) + EOF + loadsrc led_activity + if led_activities_supported "$1"; then + cat >&2 <<-EOF + activity + Blink with specific device activity (use -h right after for more info) + EOF + fi + cat >&2 <<-EOF + animate + Perform specific animation (use -h right after for more info) + inherit + Inherit status from lower priority level + ignore + Rainbow should ignore this led all together + Options: + -l Show configuration only for the current priority and slot + -h Print this help text and exit + EOF +} + +op_led() { + local id="$1"; shift + local localconf="n" + while getopts "lh" opt; do + case "$opt" in + l) + localconf="y" + ;; + h) + led_help "$id" + exit 0 + ;; + *) + led_usage "$id" + exit 2 + ;; + esac + done + shift $((OPTIND - 1)) + + local mode="" mode_args="" + local color_r="" color_g="" color_b="" + while [ $# -gt 0 ]; do + if [ -z "$color_r" ] && [ -z "$color_g" ] && [ -z "$color_b" ]; then + if parse_color "$1" inherit; then + shift + continue + fi + fi + if [ -z "$mode" ]; then + case "$1" in + auto|disable|enable|ignore) + mode="$1" + shift + continue + ;; + activity) + loadsrc led_activity + if led_activities_supported "$id"; then + mode="$1" + shift + loadsrc led_activity + op_led_activity "$id" "$@" + shift $(($# - SHIFTARGS)) + continue + fi + ;; + animate) + mode="$1" + shift + loadsrc led_animate + op_led_animate "$id" "$@" + shift $(($# - SHIFTARGS)) + continue + ;; + inherit) + mode="-" + ;; + esac + fi + break + done + SHIFTARGS=$# + + id="$(canonled "$id")" + + config() { + if [ "$id" = "all" ] || [ "$led" = "$id" ]; then + printf "%s\t%s\t%s\t%s\n" "$color_r" "$color_g" "$color_b" \ + "$mode${mode_args:+ }$mode_args" >&2 + fi + } + if [ -z "$mode" ] && [ -z "$color_r" ] && [ -z "$color_g" ] && [ -z "$color_b" ]; then + if [ "$localconf" = "y" ]; then + call_for_leds_config_level "$priority" "$slot" config + else + loadsrc uci + base_config + call_for_leds_config config + fi + return + fi + + modify() { + if [ "$id" = "all" ] || [ "$led" = "$id" ]; then + [ -z "$1" ] || color_r="$1" + [ -z "$2" ] || color_g="$2" + [ -z "$3" ] || color_b="$3" + [ -z "$4" ] || { + mode="$4" + mode_args="$5" + } + fi + } + call_for_leds_config_level "$priority" "$slot" modify \ + "$color_r" "$color_g" "$color_b" "$mode" "$mode_args" + + apply_needed="y" +} diff --git a/hardware/rainbow/files/led_activity.sh b/hardware/rainbow/files/led_activity.sh new file mode 100644 index 0000000000000000000000000000000000000000..a0354ebcdbcadbd6556124dd31b55fbbf3880d7d --- /dev/null +++ b/hardware/rainbow/files/led_activity.sh @@ -0,0 +1,173 @@ +led_activity_usage() { + echo "Usage: $0 $1 activity [OPTION].. [TRIGGER]" >&2 +} +led_activity_help() { + led_usage "$1" + cat >&2 <<-EOF + Led mode '$1' that blinks with specific device activity. + + Options: + -l List available activity triggers for this led and exit + -h Print this help text and exit + EOF +} + +op_led_activity() { + local id="$1"; shift + local list="n" + while getopts "t:lih" opt; do + case "$opt" in + l) + list="y" + ;; + h) + led_activity_help "$id" + exit 0 + ;; + *) + led_activity_usage "$id" + exit 2 + ;; + esac + done + shift $((OPTIND - 1)) + + if [ "$list" = "y" ]; then + all_supported_activities "$id" + exit 0 + fi + + # Note: we do not support every single activity trigger. For example we only + # support trigger that do both TX and RX not the separate ones. + # Warning: this sets mode_args as this is expected to be called only from + # op_led function! + mode_args="" + case "${1:-}" in + "") + mode_args="activity" + shift || true + ;; + activity|activity-inverted|heartbeat|heatbeat-inverted|disk-activity|usbport) + mode_args="$1" + shift + ;; + activity-inverted|heartbeat-inverted) + ;; + netdev-*) + # We do not intentionally verify that device exists as this way the + # hotplug devices can be configured as well. + mode_args="netdev\t${1#netdev-}" + shift + ;; + mmc*|phy*|ata*|netfilter-*) + [ -n "$(supported_activities "$id" -xF "$1")" ] || { + echo "The requested activity is invalid: $1" >&2 + usage + exit 2 + } + mode_args="$1" + shift + ;; + esac + SHIFTARGS=$# +} + +# Provide supported activities that are filtered using grep +supported_activities() { + local led + led="$(canonled "$1")"; shift + if [ "$led" = "all" ]; then + for led in $LEDS; do + supported_activities "$led" "$@" \ + | sort -u + done + else + tr ' ' '\n' <"$(led2sysfs "$led")/trigger" \ + | grep "$@" || true + fi +} + +# Lists all supported activities for given led. +all_supported_activities() { + local led + led="$(canonled "$1")" + supported_activities "$led" -xF "activity" \ + && echo "activity-inverted" + supported_activities "$led" -xF "heartbeat" \ + && echo "heartbeat-inverted" + supported_activities "$led" \ + -E "^(disk-activity|usbport|(mmc|ata)[0-9]+|netfilter-.*)$" + supported_activities "$led" -E "^phy[0-9]+tpt$" | sed 's/tpt$//' + if [ -n "$(supported_activities "$led" -E "netdev")" ]; then + for netdev in /sys/class/net/*; do + [ -e "$netdev" ] || continue + echo "netdev-${netdev##*/}" + done + fi +} + +# Check if activities are even supported for given led. +led_activities_supported() { + type led2sysfs >/dev/null || return + local led="$1" + # The activities are supported only if we have kernel driver and that + if [ "$led" = "all" ]; then + for led in $LEDS; do + # We report that at least one led supports it here + led_activities_supported "$led" && return + done + else + led="$(canonled "$led")" + led2sysfs "$led" >/dev/null \ + && [ -n "$(all_supported_activities "$led")" ] + fi +} + + +# This function applies activity trigger to led. This is kind of non-standard as +# this is called from backend.sh (from set_led) but this is same for any +# activity capable led. +apply_activity() { + local led="$1"; shift + local trigger="${1:-}" + local sysfs + sysfs="$(led2sysfs "$led")" + case "$trigger" in + activity|activity-inverted|heartbeat|heatbeat-inverted) + local systrigger="${trigger%-inverted}" + echo "$systrigger" >"$sysfs/trigger" + grep -qF "[$systrigger]" "$sysfs/trigger" + if [ "${trigger%-inverted}" == "$trigger" ]; then + echo "0" >"$sysfs/invert" + else + echo "1" >"$sysfs/invert" + fi + ;; + disk-activity|mmc*|ata*|netfilter-*) + echo "$trigger" >"$sysfs/trigger" + grep -qF "[$trigger]" "$sysfs/trigger" + ;; + usbport) + echo "$trigger" >"$sysfs/trigger" + grep -qF "[$trigger]" "$sysfs/trigger" || return + for port in "$sysfs"/ports/*; do + echo 1 > "$port" + done + ;; + phy*) + echo "${trigger}tpt" >"$sysfs/trigger" + grep -qF "[${trigger}tpt]" "$sysfs/trigger" + ;; + netdev-*) + echo "netdev" >"$sysfs/trigger" + grep -qF '[netdev]' "$sysfs/trigger" || return + echo "${trigger#netdev-}" >"$sysfs/device_name" + echo 1 >"$sysfs/link" + echo 1 >"$sysfs/rx" + echo 1 >"$sysfs/tx" + ;; + *) + internal_error "Unsupported activity arguments:" "$trigger" + ;; + esac +} diff --git a/hardware/rainbow/files/led_animate.sh b/hardware/rainbow/files/led_animate.sh new file mode 100644 index 0000000000000000000000000000000000000000..323c28f937a5ce7cd7053d845772d07c208fda1a --- /dev/null +++ b/hardware/rainbow/files/led_animate.sh @@ -0,0 +1,42 @@ +loadsrc animation + +led_animate_usage() { + echo "Usage: $0 $1 animate [OPTION].. [COLOR TIME].." >&2 +} +led_animate_help() { + led_usage "$1" + cat >&2 <<-EOF + Led mode for '$1' that autonomously changes color based on pattern. + The pattern is combination of color and time. The time is in + miliseconds. The color is of the same format as when led color is being + specified. This includes the inheritence which refers to the color of + led outside of the animation. + + Options: + -h Print this help text and exit + EOF +} + +op_led_animate() { + local id="$1"; shift + while getopts "h" opt; do + case "$opt" in + h) + led_animate_help "$id" + exit 0 + ;; + *) + led_animate_usage "$id" + exit 2 + ;; + esac + done + shift $((OPTIND - 1)) + + local animation + parse_animation "$@" + shift $(($# - SHIFTARGS)) + mode_args="$animation" + + SHIFTARGS=$# +} diff --git a/hardware/rainbow/files/ledid.sh b/hardware/rainbow/files/ledid.sh new file mode 100644 index 0000000000000000000000000000000000000000..fc649c47bcc2939a21b3ae6dd2475c0fd3ad84ef --- /dev/null +++ b/hardware/rainbow/files/ledid.sh @@ -0,0 +1,38 @@ +# Check if provided led identifier is valid. +# This covers not only those fron LEDS but also ledX where X is number and all. +is_valid_led() { + local check_led="$1" + [ "$check_led" = "all" ] \ + && return 0 + local i=0 + for led in $LEDS; do + if [ "$check_led" = "$led" ]; then + return 0 + fi + i=$((i + 1)) + done + if [ "${check_led#led}" != "$check_led" ] \ + && [ "${check_led#led}" -gt 0 ] && [ "${check_led#led}" -le "$i" ]; then + return 0 + fi + return 1 +} + + +# Convert any other representation (current only ledX) to thos from LEDS +# variable or 'all'. +canonled() { + local value="$1" + local index=1 + local led + for led in $LEDS; do + if [ "$value" = "$led" ] || { + [ "${value#led}" != "$value" ] && [ "${value#led}" -eq $index ] + }; then + echo "$led" + return + fi + index=$((index + 1)) + done + echo "$value" +} diff --git a/hardware/rainbow/files/ledid.test.sh b/hardware/rainbow/files/ledid.test.sh new file mode 100755 index 0000000000000000000000000000000000000000..6f18848ae33df3a62f69c0a598204497f1337045 --- /dev/null +++ b/hardware/rainbow/files/ledid.test.sh @@ -0,0 +1,49 @@ +#!/bin/ash +set -eu +. "${0%/*}/ledid.sh" + +fail() { + echo "$@" >&2 + exit 1 +} + +LEDS="pwr lan0 lan1 pci1 usr1" + +valid() { + is_valid_led "$1" || fail "Led should be valid but it is not: $1" +} + +not_valid() { + ! is_valid_led "$1" || fail "Led should not be valid but it is: $1" +} + +for led in $LEDS; do + valid "$led" +done +not_valid "power" +not_valid "usr" +not_valid "p" +not_valid "1" + +for i in $(seq 5); do + valid "led$i" +done +not_valid "led6" +not_valid "led7" + + +canon() { + local value="$1" + local expected="$2" + local res + res="$(canonled "$value")" || fail "Led canon failed for: $value" + [ "$res" = "$expected" ] || fail "Led canon is not correct: $res != $expected" +} + +index=1 +for led in $LEDS; do + canon "$led" "$led" + canon "led$index" "$led" + index=$((index + 1)) +done +canon "foo" "foo" diff --git a/hardware/rainbow/files/pci.sh b/hardware/rainbow/files/pci.sh new file mode 100644 index 0000000000000000000000000000000000000000..e5fb03b915f4ad9ee427ad5091cde27a938f15f7 --- /dev/null +++ b/hardware/rainbow/files/pci.sh @@ -0,0 +1,28 @@ +loadsrc led_activity + +# In some cases we need to detect what is inserted in the specific PCI slot and +# setup activity trigger from system according to that. This function tries to +# do the detection produces activity configuration. +# It expects path to sysfs to the PCI device to investigate. +# This echoes activity argument for activity status. +# If no device was detected or if activity for it is not supported it signals +# failure with returned value. +pci_device_activity_detect() { + local syspci="$1" + local led="$1" + + if [ -d "$syspci/ieee80211" ]; then + [ -n "$(supported_activities "$led" -xF 'netdev')" ] || return + echo "netdev-$(ls -1 "$syspci/net" | head -1)" + return + fi + + for ata in "$syspci"/ata*; do + [ -d "$ata" ] || continue + [ -n "$(supported_activities "$led" -xF "$ata")" ] || continue + echo "${ata##*/}" + return + done + + return 1 +} diff --git a/hardware/rainbow/files/rainbow-animator.init b/hardware/rainbow/files/rainbow-animator.init new file mode 100755 index 0000000000000000000000000000000000000000..d30759fcd43a7f7af9c170720efe96dd4c8cf013 --- /dev/null +++ b/hardware/rainbow/files/rainbow-animator.init @@ -0,0 +1,27 @@ +#!/bin/sh /etc/rc.common + +START=98 +STOP=01 + +USE_PROCD=1 + +reload_service() { + procd_send_signal "$(basename "$(readlink -f "$initscript")")" '*' SIGUSR2 +} + +extra_command "pause" "Pause animator loop" +pause() { + procd_send_signal "$(basename "$(readlink -f "$initscript")")" '*' SIGUSR1 +} + +start_service() { + config_load rainbow + config_get animation_ups "animation" "ups" "15" + + procd_open_instance rainbow + procd_set_param command /usr/libexec/rainbow/animator.py --ups "$animation_ups" + procd_set_param respawn 600 5 5 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} diff --git a/hardware/rainbow/files/rainbow-button-sync.init b/hardware/rainbow/files/rainbow-button-sync.init new file mode 100755 index 0000000000000000000000000000000000000000..ab5ba0f1f2430d7c6b9300af3ddc833b8f95eca8 --- /dev/null +++ b/hardware/rainbow/files/rainbow-button-sync.init @@ -0,0 +1,15 @@ +#!/bin/sh /etc/rc.common + +START=98 +STOP=01 + +USE_PROCD=1 + +start_service() { + procd_open_instance + procd_set_param command /usr/libexec/rainbow/button_sync.sh + procd_set_param respawn 600 5 5 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} diff --git a/hardware/rainbow/files/rainbow.init b/hardware/rainbow/files/rainbow.init new file mode 100755 index 0000000000000000000000000000000000000000..356d49a0c4e9992a71f5c69ad8a725d10029035c --- /dev/null +++ b/hardware/rainbow/files/rainbow.init @@ -0,0 +1,22 @@ +#!/bin/sh /etc/rc.common + +# Start at the end, so the colors change after the boot is done. +START=99 +STOP=00 + +reload() { + rainbow reset -n +} + +restart() { + rainbow reset -a +} + +start() { + rainbow reset -a -b & +} + +stop() { + # Reset to the default, so a reboot can be recognized. + rainbow -p 100 -n shutdown all enable white +} diff --git a/hardware/rainbow/files/rainbow.sh b/hardware/rainbow/files/rainbow.sh new file mode 100755 index 0000000000000000000000000000000000000000..d1f1dae7e329e702d946cec5f9113a00d528d387 --- /dev/null +++ b/hardware/rainbow/files/rainbow.sh @@ -0,0 +1,152 @@ +#!/bin/sh +set -eu + +rainbowdir="/var/run/rainbow" +mkdir -p "$rainbowdir" +[ "${RAINBOW_DIRECTORY_LOCKED-:}" = "y" ] \ + || RAINBOW_DIRECTORY_LOCKED="y" exec flock -Fx "$rainbowdir" "$0" "$@" + +. "$(dirname "$(readlink -f "$0")")/utils.sh" +loadsrc state +loadsrc ledid +loadsrc backend + +################################################################################ +usage() { + echo "Usage: $0 [OPTION].. ..." >&2 +} +help() { + usage + cat >&2 <<-EOF + Turris leds manipulation. + + Options: + -p INT Set priority to given led setting (in default 50 is used) + -n STR Slot name ('default' used if not specified) + -l List all available LEDs by name + -x Run in debug mode + -h Print this help text and exit + Operations: + reset Reset rainbow state (wipe any runtime changes) + brightness Set upper limit on brightness of all leds + all Configure all available leds at once + ledX Where X is number (up to 12 on Omnia and 8 on 1.x) + Configure specific led by name: + $LEDS + EOF +} + +priority=50 +slot="default" +list_leds="n" +while getopts "p:n:lxh" opt; do + case "$opt" in + p) + if [ "$OPTARG" -ge 0 ] && [ "$OPTARG" -le 100 ]; then + priority="$OPTARG" + else + echo "Priority is not in range from 0 to 100: $OPTARG" >&2 + usage + fi + ;; + n) + if [ "${OPTARG#*/}" != "$OPTARG" ]; then + echo "The slot name can't contain '/'" >&2 + usage + exit 2 + fi + slot="$OPTARG" + ;; + l) + list_leds="y" + ;; + x) + set -x + ;; + h) + help + exit 0 + ;; + *) + usage + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ "$list_leds" = "y" ]; then + for led in $LEDS; do + echo "$led" + done + exit 0 +fi + +[ $# -gt 0 ] || { + usage + exit 2 +} + +apply_needed="n" +while [ $# -gt 0 ]; do + operation="${1:-}" + shift + case "$operation" in + reset) + loadsrc reset + op_reset "$@" + shift $SHIFTARGS + ;; + brightness) + loadsrc brightness + op_brightness "$@" + shift $(($# - SHIFTARGS)) + ;; + "") + usage + exit 2 + ;; + *) + if is_valid_led "$operation"; then + loadsrc led + op_led "$operation" "$@" + shift $(($# - SHIFTARGS)) + else + SHIFTARGS=$# + type compatibility >/dev/null \ + && compatibility "$operation" "$@" + if [ "$SHIFTARGS" -lt $# ]; then + shift $(($# - SHIFTARGS)) + else + echo "Invalid operation: $operation" >&2 + usage + exit 2 + fi + fi + ;; + esac +done + +################################################################################ +# Helper function that is used in combination with parse_leds_file. +# This takes variables and passes them as an appropriate arguments as set_led +# functions expect it. +call_set_led() { + [ "$mode" != "ignore" ] || return 0 + shift $# # we do not care about our arguments + set "$@" "$led" "$color_r" "$color_g" "$color_b" "$mode" + while read -r arg; do + set "$@" "$arg" + done <<-EOF + $(echo "$mode_args" | tr '\t' '\n') + EOF + set_led "$@" +} + +loadsrc uci +if [ "$apply_needed" = "y" ]; then + base_config + ! type preapply >/dev/null || preapply + call_for_leds_config call_set_led + ! type postapply >/dev/null || postapply +fi diff --git a/hardware/rainbow/files/reset.sh b/hardware/rainbow/files/reset.sh new file mode 100644 index 0000000000000000000000000000000000000000..5a6e9097974ef6f69c6f834ea5a5c9b6d0e7e072 --- /dev/null +++ b/hardware/rainbow/files/reset.sh @@ -0,0 +1,58 @@ +reset_usage() { + echo "Usage: $0 reset [OPTION].." >&2 +} +reset_help() { + reset_usage + cat >&2 <<-EOF + Reset current runtime configuration. + + Options: + -n Do not remove current configuration only apply it + -a Reset all priority levels and slots not just the current one + -b Run boot sequence as part of reset + -h Print this help text and exit + EOF +} + +op_reset() { + local nowipe="n" + local all="n" + local bootseq="n" + while getopts "nabh" opt; do + case "$opt" in + n) + nowipe="y" + ;; + a) + all="y" + ;; + b) + bootseq="y" + ;; + h) + reset_help + exit 0 + ;; + *) + reset_usage + exit 2 + ;; + esac + done + SHIFTARGS=$((OPTIND - 1)) + + if [ "$nowipe" = "n" ]; then + if [ "$all" = "y" ]; then + rm -f "$rainbowdir"/* + else + rm -f "$rainbowdir/$(printf "%03d" "$priority")-$slot" + fi + fi + + # Note: the boot sequence is silently ignored if there is none defined + [ "$bootseq" = "y" ] \ + && type boot_sequence >/dev/null \ + && boot_sequence + + apply_needed="y" +} diff --git a/hardware/rainbow/files/state.sh b/hardware/rainbow/files/state.sh new file mode 100644 index 0000000000000000000000000000000000000000..4fcba2379e18322e3ed0b28d57c0fd40877ef7de --- /dev/null +++ b/hardware/rainbow/files/state.sh @@ -0,0 +1,76 @@ +# State directory helper functions + +# Parses stdin where it expects to get lines for leds. The provided function is +# called with provided arguments. +# The function provides variables: +# led: with led name (from LEDS list) +# color_r: 0-255 value of red color +# color_g: 0-255 value of green color +# color_b: 0-255 value of blue color +# mode: mode of led +# mode_args: Any argument passed after mode (which are mode arguments) +# It echoes the possibly updated state after function call (feel free to discart +# output if you do not update variables). +parse_leds_file() { + local func="$1"; shift + local led + for led in $LEDS; do + local color_r="-" color_g="-" color_b="-" mode="-" mode_args + read -r color_r color_g color_b mode mode_args + "$func" "$@" &2 + printf "%s\t%s\t%s\t%s" "$color_r" "$color_g" "$color_b" "$mode" + [ -n "$mode_args" ] \ + && printf "\t%s" "$mode_args" + printf "\n" + done +} + +# Call provided function with configuration for specified priority. +# The provided variables are the same as parse_leds_file and their modification +# is saved back to the file. +call_for_leds_config_level() { + local priority="$1" + local slot="$2" + local fnc="$3" + shift 3 + local file="$rainbowdir/$(printf "%03d" "$priority")-$slot" + { + if [ -f "$file" ]; then + cat "$file" + else + for led in $LEDS; do + printf -- "-\t-\t-\t-\n" + done + fi + } | parse_leds_file "$fnc" "$@" > "$file.new" + if grep -qv '^-\t-\t-\t-' "$file.new"; then + mv "$file.new" "$file" + else + # The configuration is empty so we no longer need it + rm -f "$file" "$file.new" + fi +} + +# Calls provided function with assembled configuration (combination of all +# configured levels). The provided variables are the same as parse_leds_file. +call_for_leds_config() { + local fnc="$1"; shift + awk -F'\t' ' + { + for (i=1; i<=NF; i++) + if (fields[FNR,i] == "" || $i != "-") + fields[FNR,i]=$i + } + END { + OFS="\t" + for (x = 1; x <= FNR; x++) { + $0="" + y = 1 + while ((x,y) in fields) + $y=fields[x,y++] + print + } + } + ' "$rainbowdir"/[0-9][0-9][0-9]-* \ + | parse_leds_file "$fnc" "$@" >/dev/null +} diff --git a/hardware/rainbow/files/state.test.sh b/hardware/rainbow/files/state.test.sh new file mode 100755 index 0000000000000000000000000000000000000000..e8608e021716341622150ef86e02fc0ebacd3b5f --- /dev/null +++ b/hardware/rainbow/files/state.test.sh @@ -0,0 +1,119 @@ +#!/bin/ash +set -eu +loadsrc() { + . "${0%/*}/$1.sh" +} +loadsrc state + +rainbowdir="$(mktemp -d)" +trap 'rm -rf "$rainbowdir"' EXIT + +LEDS="pwr lan0 lan1 pci1 usr1" + +fail() { + echo "$@" >&2 + exit 1 +} + +cmp_file() ( + local level="$1" + local slot="$2" + cat >"$rainbowdir/.cmpfile" + trap 'rm -f "$rainbowdir/.cmpfile"' EXIT + diff "$rainbowdir/.cmpfile" "$rainbowdir/$(printf "%03d" "$level")-$slot" \ + || fail "Files are different" +) + + +default() { + true +} +call_for_leds_config_level 50 "default" default +cmp_file 50 "default" <"$rainbowdir/000-base" + + config_load system + for led in $LEDS; do + color_r="-" color_g="-" color_b="-" mode="-" mode_args="" + sysfs="$(led2sysfs "$led")"; sysfs="${sysfs##*/}" + config_foreach _uci_system_section "led" + echo "$color_r $color_g $color_b $mode${mode_args:+ }$mode_args" + done >"$rainbowdir/000-system" + ) + _base_config_updated="y" +} + +# Parse given led setting from UCI +_uci_rainbow_section() { + local led="$1" + color_r="$default_r" color_g="$default_g" color_b="$default_b" + mode="$default_mode" mode_args="$default_mode_args" + ! type led_defaults >/dev/null || led_defaults "$led" + + config_get rawcolor "$led" "color" "" + parse_color "$rawcolor" + + config_get rawmode "$led" "state" "" # backward compatibility + config_get rawmode "$led" "mode" "$rawmode" + [ -n "$rawmode" ] || return 0 + mode="$rawmode" + mode_args="" + if [ "$mode" = "activity" ]; then + config_get trigger "$led" "trigger" "activity" + mode_args="$trigger" + elif [ "$mode" = "animate" ]; then + local animation animation_color_r animation_color_g animation_color_b + animation_init + config_list_foreach "$led" "$animation" _for_uci_animation + animation_finish + mode_args="$animation" + fi +} + +_for_uci_animation() { + local value="$1" + read -r color time <<-EOF + $value + EOF + animation_add "$color" "$time" \ + || warning "Skipping invalid animation state for '$led': $1" +} + +_uci_system_section() { + local section="$1" + local cursysfs + config_get cursysfs "$section" "sysfs" + if [ "$cursysfs" = "$sysfs" ]; then + local default trigger + config_get_bool default "$section" "default" "0" + config_get trigger "$section" "trigger" "none" + case "$trigger" in + none) + [ "$default" != "1" ] \ + || mode="enable" + ;; + time) + local on off + config_get on "$section" "delayon" + config_get off "$section" "delayoff" + if [ -n "$on" ] && [ -n "$off" ]; then + mode="animate" + mode_args="black $off black 0 - $on - 0" + fi + ;; + default-on) + mode="enable" + ;; + heatbeat) + mode="activity" + if [ "$default" = "0" ]; then + mode_args="heatbeat" + else + mode_args="heatbeat-inverted" + fi + ;; + netdev) + local dev + config_get dev "$section" "dev" + if [ -n "$dev" ]; then + mode="activity" + mode_args="netdev-$dev" + fi + # We ignore mode as rainbow does not support it + ;; + phy*) + mode="activity" + mode_args="${trigger%${trigger#phy[0-9]}}" + ;; + usbdev|usbport) + mode="activity" + mode_args="usbport" + # We ignore dev as rainbow simply follows all USB devices + ;; + *) + warning "Unsupported trigger '$trigger' for: system.$section ($sysfs)" + ;; + esac + fi +} diff --git a/hardware/rainbow/files/utils.sh b/hardware/rainbow/files/utils.sh new file mode 100644 index 0000000000000000000000000000000000000000..b1a8fd698aa20119ba209ec4a597d2ed61684930 --- /dev/null +++ b/hardware/rainbow/files/utils.sh @@ -0,0 +1,29 @@ +# Common utilities for rainbow scripts + +# All rainbow scripts should set this but by adding it here as well we make sure +set -eu + +warning() { + echo "Warning:" "$@" >&2 +} + +fail() { + echo "$0:" "$@" >&2 + exit 1 +} + +internal_error() { + echo "$0: Internal error:" "$@" >&2 + exit 3 +} + +# This function should be used to load any other source files that are part of +# rainbow except of this one. +srcdir="$(dirname "$(readlink -f "$0")")" +loadsrc() { + local name="$1" + if ! echo "${_src_loaded:-}" | grep -qF "+$name+"; then + . "$srcdir/$name.sh" + _src_loaded="${_src_loaded:-}+$name+" + fi +} diff --git a/hardware/turris1x/turris1x-rainbow-backend/Makefile b/hardware/turris1x/turris1x-rainbow-backend/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4d92a07267ebe22d97aeac8bd12721e0310c20b5 --- /dev/null +++ b/hardware/turris1x/turris1x-rainbow-backend/Makefile @@ -0,0 +1,38 @@ +# +## Copyright (C) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) +# +## This is free software, licensed under the GNU General Public License v3. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=turris1x-rainbow-backend +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=CZ.NIC +PKG_LICENSE:=GPL-3.0-or-later +PKG_LICENSE_FILES:=LICENSE + +include $(INCLUDE_DIR)/package.mk + +define Package/turris1x-rainbow-backend + TITLE:=turris1x-rainbow-backend + DEPENDS:=\ + @KERNEL_DEVMEM @TARGET_mpc85xx_p2020_DEVICE_turris1x \ + +turris1x-rainbow \ + +rainbow-button-sync \ + +rainbow-animator + PROVIDES:=rainbow-backend +endef + +Build/Compile:=: + +define Package/turris1x-rainbow-backend/install + $(INSTALL_DIR) $(1)/usr/share + $(INSTALL_DATA) ./files/backend.sh $(1)/usr/libexec/rainbow/ + $(INSTALL_DATA) ./files/animator_backend.sh $(1)/usr/libexec/rainbow/ +endef + +$(eval $(call BuildPackage,turris1x-rainbow-backend)) diff --git a/hardware/turris1x/turris1x-rainbow-backend/files/animator_backend.py b/hardware/turris1x/turris1x-rainbow-backend/files/animator_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..f1c6598afa51abc6f746286d28319581bcf22890 --- /dev/null +++ b/hardware/turris1x/turris1x-rainbow-backend/files/animator_backend.py @@ -0,0 +1,27 @@ +import subprocess + + +class Backend: + """Handler for all leds we can control.""" + + # All available leds in order on the box + LEDS = ["wan", "lan1", "lan2", "lan3", "lan4", "lan5", "wifi", "pwr"] + + def __init__(self): + self.args = [] + + def update(self, ledid: int, red: int, green: int, blue: int) -> None: + """Update color of led on given index.""" + self.args.append(self.LEDS[ledid]) + self.args.append("%.2X%.2X%.2X" % red, green, blue) + + def apply(self) -> None: + """Apply LEDs state updates.""" + self.args.insert(0, "turris1x-rainbow") + subprocess.run(self.args, check=True) + self.args.clear() + + @staticmethod + def handled(ledid: int) -> bool: + """Informs animator if given led animation should be handled by it.""" + return True # On 1.x all leds have to be animated using animator diff --git a/hardware/turris1x/turris1x-rainbow-backend/files/backend.sh b/hardware/turris1x/turris1x-rainbow-backend/files/backend.sh new file mode 100644 index 0000000000000000000000000000000000000000..20969f44b2b9e2ad0c6b57a810aa31d0c0610c63 --- /dev/null +++ b/hardware/turris1x/turris1x-rainbow-backend/files/backend.sh @@ -0,0 +1,89 @@ +# Turris 1.x backend for generic rainbow script +LEDS="wan lan1 lan2 lan3 lan4 lan5 wifi pwr" + +led_defaults() { + local led="$1" + case "$led" in + pwr) + color_r=0 color_g=255 color_b=0 + ;; + lan*|wifi) + color_r=0 color_g=0 color_b=255 + ;; + wan) + color_r=0 color_g=255 color_b=255 + ;; + esac +} + +get_brightness() { + local cur + cur="$(turris1x-rainbow get intensity)" + echo $((cur * 255 / 7)) +} + +preapply() { + /etc/init.d/rainbow-animator pause + + local brightness_level + brightness_level="$(brightness)" + # TODO we might want to use brightness of leds to tweak brightness itself + turris1x_rainbow_args="intensity $((brightness_level * 7 / 255))" +} + +set_led() { + local led="$1" r="$2" g="$3" b="$4" mode="$5" + shift 5 + + turris1x_rainbow_args="${turris1x_rainbow_args}${turris1x_rainbow_args:+ }$led" + + turris1x_rainbow_args="${turris1x_rainbow_args} $(printf "%.2X%.2X%.2X" "$r" "$g" "$b")" + + case "$mode" in + auto|disable|enable) + turris1x_rainbow_args="${turris1x_rainbow_args} $mode" + ;; + animate) + turris1x_rainbow_args="${turris1x_rainbow_args} enable" + # For animation we we rely on animator + ;; + *) + internal_error "Unsupported mode:" "$mode" + ;; + esac +} + +postapply() { + [ -z "$turris1x_rainbow_args" ] \ + || turris1x-rainbow $turris1x_rainbow_args + /etc/init.d/rainbow-animator reload +} + + +boot_sequence() { + turris1x-rainbow all green enable + sleep 1 + turris1x-rainbow all blue enable + sleep 1 + # Note: we apply in the end so we don't have to restore previous state +} + + +# Parse operations that were previously available with rainbow +compatibility() { + local operation="$1"; shift + SHIFTARGS=$# + case "$operation" in + binmask) + compat_binmask "$@" + ;; + intensity) + compat_intensity 7 "$@" + ;; + get) + compat_get 7 "$@" + ;; + esac + shift $(($# - SHIFTARGS)) + SHIFTARGS=$# +} diff --git a/hardware/turris1x/turris1x-rainbow/Makefile b/hardware/turris1x/turris1x-rainbow/Makefile index aaac87bfc716ca131566da37862b0102cb767b52..4d3d3e2a45b2fd0115098b6e08d5c37aba8150ef 100644 --- a/hardware/turris1x/turris1x-rainbow/Makefile +++ b/hardware/turris1x/turris1x-rainbow/Makefile @@ -1,5 +1,5 @@ # -## Copyright (C) 2013-2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) +## Copyright (C) 2013-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) # ## This is free software, licensed under the GNU General Public License v3. # See /LICENSE for more information. @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=turris1x-rainbow PKG_VERSION:=17 -PKG_RELEASE:=3 +PKG_RELEASE:=4 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://gitlab.nic.cz/turris/rainbow.git @@ -25,30 +25,12 @@ include $(INCLUDE_DIR)/package.mk define Package/turris1x-rainbow TITLE:=turris1x-rainbow URL:=https://gitlab.nic.cz/turris/rainbow - PROVIDES:=turris-rainbow DEPENDS:=@KERNEL_DEVMEM @TARGET_mpc85xx_p2020_DEVICE_turris1x endef -define Package/turris1x-rainbow/conffiles -/etc/config/rainbow -endef - define Package/turris1x-rainbow/install $(INSTALL_DIR) $(1)/usr/bin/ - $(INSTALL_BIN) $(PKG_BUILD_DIR)/rainbow $(1)/usr/bin/ - $(INSTALL_DIR) $(1)/etc/init.d/ - $(INSTALL_BIN) ./files/rainbow.init $(1)/etc/init.d/rainbow - $(INSTALL_DIR) $(1)/etc/config/ - $(INSTALL_DATA) ./files/rainbow.config $(1)/etc/config/rainbow - $(INSTALL_DIR) $(1)/etc/cron.d - $(INSTALL_DATA) ./files/rainbow.cron $(1)/etc/cron.d/rainbow - $(INSTALL_BIN) ./files/rainbow_button_sync.sh $(1)/usr/bin/rainbow_button_sync.sh -endef - -define Package/turris1x-rainbow/prerm -#!/bin/sh -[ -n "$$IPKG_INSTROOT" ] || \ - rainbow all auto white + $(INSTALL_BIN) $(PKG_BUILD_DIR)/rainbow $(1)/usr/bin/turris1x-rainbow endef $(eval $(call BuildPackage,turris1x-rainbow)) diff --git a/hardware/turris1x/turris1x-rainbow/files/rainbow.config b/hardware/turris1x/turris1x-rainbow/files/rainbow.config deleted file mode 100644 index bbaceb1a22a1db6dafa904d3c03336741608635f..0000000000000000000000000000000000000000 --- a/hardware/turris1x/turris1x-rainbow/files/rainbow.config +++ /dev/null @@ -1,25 +0,0 @@ -# You can set all the leds at once. -config led 'all' - option color 'turris-default' - option status 'auto' - -# Any other config will overwrite the one set in all -#config led 'pwr' -# option color 'FF0066' -# option status 'auto' -# -#config led 'wifi' -# option color 'FFFF00' -# option status 'auto' -# -#config led 'lan' -# option color 'green' -# option status 'auto' -# -#config led 'wan' -# option color 'blue' -# option status 'auto' - -# The ones with lan can't set color and the status overwrites the one in the 'lan' section, if mentioned -#config led 'lan4' -# option status 'disabled' diff --git a/hardware/turris1x/turris1x-rainbow/files/rainbow.cron b/hardware/turris1x/turris1x-rainbow/files/rainbow.cron deleted file mode 100644 index cb4d5923d07cf057c061c73d1333a832d091d61a..0000000000000000000000000000000000000000 --- a/hardware/turris1x/turris1x-rainbow/files/rainbow.cron +++ /dev/null @@ -1,2 +0,0 @@ -MAILTO="" -* * * * * root /usr/bin/rainbow_button_sync.sh diff --git a/hardware/turris1x/turris1x-rainbow/files/rainbow.init b/hardware/turris1x/turris1x-rainbow/files/rainbow.init deleted file mode 100644 index 94bf1a4916351675804adaa62a8aced9caaf46e0..0000000000000000000000000000000000000000 --- a/hardware/turris1x/turris1x-rainbow/files/rainbow.init +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh /etc/rc.common - -# Start at the end, so the colors change after the boot is done. -START=99 -STOP=00 - -PID_FILE="/var/run/rainbow.pid" - -start() { - # Signal the boot is finished - rainbow all enable blue - sleep 1 - rainbow all enable green - sleep 1 - # Set the colors based on user preferrences - uci show rainbow | sed -ne 's/rainbow\.\([^.]*\)=led/\1/p' | sort | while read SECTION ; do - STATUS=$(uci get rainbow.$SECTION.status) - COLOR=$(uci get rainbow.$SECTION.color) - rainbow $SECTION $STATUS $COLOR - done - # Resume last intensity settings - rainbow intensity $(cat /etc/rainbow.magic) - - # Setup LEDs if enabled - if [ -x /etc/init.d/setup_led ] && /etc/init.d/setup_led enabled; then - /etc/init.d/setup_led restart - fi - - # Start the daemon - if [ -s $PID_FILE ]; then - echo "Daemon is alredy running..." - else - rainbow --daemonize - fi -} - -stop() { - # Reset to the default, so a reboot can be recognized. - rainbow all enable white - kill -9 $(cat $PID_FILE) - rm $PID_FILE -} diff --git a/hardware/turris1x/turris1x-rainbow/files/rainbow_button_sync.sh b/hardware/turris1x/turris1x-rainbow/files/rainbow_button_sync.sh deleted file mode 100755 index 5f30d9f3cb61881baf6388a5e8ac96e0338e2305..0000000000000000000000000000000000000000 --- a/hardware/turris1x/turris1x-rainbow/files/rainbow_button_sync.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -MAGIC_FILE="/etc/rainbow.magic" - -# Get actual intensity -ACT_INTENSITY=$(rainbow get intensity) - -# Get value from magic file -[ -f "$MAGIC_FILE" ] || echo 0 > "$MAGIC_FILE" -LAST_VALUE=$(cat "$MAGIC_FILE") - -# Compare them and if last value is different from stored one update it -if [ "$ACT_INTENSITY" != "$LAST_VALUE" ]; then - echo $ACT_INTENSITY > "$MAGIC_FILE" -fi