-
Notifications
You must be signed in to change notification settings - Fork 1
RGB Control
The RGB-Control board shown below can drive an RGB-LED-strip and set its color interactively using buttons, a rotary encoder, and a display. The Atrium firmware therefore provides the necessary drivers and infrastructure, which just need to be configured for this specific application. The concept of configurable state-machines, events, and actions makes it possible to setup very flexible applications.
Having a single firmware image configured for a specific application, makes it possible to use roll out firmware updates across multiple embedded hardware and applications without having to rebuild for all different setups. The article below, shows how to configure hardware and software in Atrium. The demonstrated approach is a major advantage of Atrium over Arduino based implementation that require one software for every individual application.
The RGB-Control board provides:
- an ESP32
- an interface for a BME280 module with I2C interface
- an interface for an OLED display with I2C interface
- 3 low-side MOSFETs
- a high-side MOSFET
- a current sensor
- a light sensor
- a rotary-encoder
- 3 push-buttons with LEDs
- additional status LEDs
- a debug and flashing UART interface
Below you can see how to configure the Atrium firmware without writing a single line of code to provide a user-interface for adjusting an RGB strip interactively, using the display, the buttons, and the rotary encoder. This is done by setting up a state-machine that routes events of the rotary encoder and buttons to each of the 3 PWM drivers for the RGB low-side MOSFETs. Adjusting the values can be observed on the display in parallel.
- flashing the Atrium firmware
- initial WiFi configuraton
- configuring drivers
- runtime setup
The binary distribution of Atrium provides 2 script to flash an ESP
device. One for initial setup called flash-atrium.sh
that flashes
the bootloader, the partition table, and the application. This script
will also erase the complete device before flashing the images.
The second one is called update-atrium.sh
, which will only update the
application image and keep everything else in place, including the
configuration.
Updating can also be performe over-the-air (OTA) via telnet
http/ftp/tftp download or via http upload using the integtated web
server if approriate .html
pages are on the device.
For initial flashing of an ESP32 with 4MB of flash, run:
$ ./flash-atrium.sh esp32_4m
Before being able to contact an Atrium device via MQTT or telnet, its
WiFi settings must be configured appropriately. As this configuration
is common to all Atrium devices, it is recommended to prepare this
configuration with the config tool atriumcfg
, so that you can
distribute it afterwards easily to multiple devices either as hex-dump
or directly as an partition upload.
But it is even easier to perform the WiFi configuration on the device with a serial console.
The on-target configuation steps are as follows:
> config set nodename <name-of-the-device>
> config set station.ssid <SSID-of-WiFi>
> config set station.pass <PSK-of-WiFi>
> config set station.activate true
> config add dns_server <dns-ip-address>
> config set domainname <domain-name>
> config set sntp_server <sntp-server-name-or-IP>
# optionally
> config set mqtt.uri mqtt://<mqtt-host>:1883
> config set mqtt.enable true
# write the configuartion to the non-volatile storage
> config write my-wifi.cfg
Setting the WiFi SSID, password, and activate
is required to
let the device contact your network. If you omit setting its nodename
,
it will create a default nodename based on its MAC address. Setting the
DNS server and domain-name is required if yout want to be able to used
hostnames instead of IP addresses.
Atrium devices are also resolvable by their nodename via mDNS in a LAN that
uses DHCP to provide an IP address to all devices in the WiFi. Just append
.local
to the nodename, when using HTTP or telnet to contact a Atrium device.
Setting an SNTP server is required if you want to use execute actions based on date or weekday and time.
Setting up MQTT is optionally and also requires that you have an MQTT server running on your network. This will enable you to interact with the Atrium device via MQTT.
To configure the hardware, you can either use a matthcing NVM image, use the hex-dump of the prepared config or follow the manual steps below.
For manual configuration you can either use the config tool atriumcfg
,
which means you have to upload the config to the target (e.g. using
telnet xxd dump or remote configuraton with atriumctrl.py
) or you can
perform the configuration on the target directly. If you use the config
tool, you must omit the prefixed hwconf
on every line, but you have to
switch to the hardware configuration in atriumcfg
using the hw
command.
Here are the command, how they are executed on the target either via telnet or via serial console.
# configure the current measurement ADC
> hwconf add adc.channels
> hwconf set adc.channels[0].name current
> hwconf set adc.channels[0].ch 0
> hwconf set adc.channels[0].unit 1
# configure the ambient light ADC
> hwconf add adc.channels
> hwconf set adc.channels[1].name amblight
> hwconf set adc.channels[1].ch 3
> hwconf set adc.channels[1].unit 1
# configure the rotary encoder
> hwconf add button
> hwconf set button[0].name rotenc
> hwconf set button[0].gpio 13
> hwconf set button[0].dt 14
> hwconf set button[0].clk 27
# configure the color buttons
> hwconf add button
> hwconf set button[1].name btnr
> hwconf set button[1].gpio 34
> hwconf add button
> hwconf set button[2].name btng
> hwconf set button[2].gpio 35
> hwconf add button
> hwconf set button[3].name btnb
> hwconf set button[3].gpio 26
# configure the LEDs on the buttons
> hwconf add led
> hwconf set led[0].name swledr
> hwconf set led[0].gpio 32
> hwconf add led
> hwconf set led[1].name swledg
> hwconf set led[1].gpio 33
> hwconf add led
> hwconf set led[2].name swledb
> hwconf set led[2].gpio 25
# configure the status LED
> hwconf add led
> hwconf set led[3].name status
> hwconf set led[3].gpio 23
# configure the application LED
> hwconf add led
> hwconf set led[4].name appled
> hwconf set led[4].gpio 23
# configure the PWM channels
> hwconf add led
> hwconf set led[5].name red
> hwconf set led[5].gpio 4
> hwconf set led[5].pwm_ch 0
> hwconf add led
> hwconf set led[6].name green
> hwconf set led[6].gpio 15
> hwconf set led[6].pwm_ch 1
> hwconf add led
> hwconf set led[7].name blue
> hwconf set led[7].gpio 17
> hwconf set led[7].pwm_ch 2
# configure the high-side MOSFET, which is shared across all LEDs
> hwconf add led
> hwconf set led[8].name hss
> hwconf set led[8].gpio 2
> hwconf set led[8].config.active_high 1
# configure the I2C bus
> hwconf set i2c.sda 19
> hwconf set i2c.scl 18
# configure the display
> hwconf set display.type dt_ssd1306
> hwconf set display.maxx 128
> hwconf set display.maxy 64
# set display option alternating rows (see ssd1306.h)
> hwconf set display.options 0x10
# finally make the config persistent
> hwconf write
# and reboot to activate the configuration
> reboot
Once the hardware configuration has been performed and activated (via
rebooting), all drivers should provide their necessary events and
actions that are needed for the run-time configuration. You can use
event -l
and action -l
to list available actions and events
accoringly.
Below follows an example how the configured drivers can be setup with a state machine to allow easy user interaction for setting the RGB values on the LED strip and storing and restoring the related PWM values.
# add a state machine for user-interface
> sm add ui
# add states for changing the individual colors and for general operation
> sm add ui main
> sm add ui red
> sm add ui green
> sm add ui blue
# Increment/decrement PWM value of relevant channel when turning rotary
# encoder when in the relevant state. I.e. red state to modify the red
# PWM.
> sm on ui:red rotenc`left red!dec
> sm on ui:red rotenc`right red!inc
> sm on ui:green rotenc`left green!dec
> sm on ui:green rotenc`right green!inc
> sm on ui:blue rotenc`left blue!dec
> sm on ui:blue rotenc`right blue!inc
# To change between the states, the buttons need to be configured. We
# start in the main state to switch to the color-changing modes.
> sm on ui:main btnr`short sm!set ui:red
> sm on ui:main btng`short sm!set ui:green
> sm on ui:main btnb`short sm!set ui:blue
# In red state we want to switch to all other states.
> sm on ui:red btnr`short sm!set ui:main
> sm on ui:red btng`short sm!set ui:green
> sm on ui:red btnb`short sm!set ui:blue
# Same in green...
> sm on ui:green btnr`short sm!set ui:red
> sm on ui:green btng`short sm!set ui:main
> sm on ui:green btnb`short sm!set ui:blue
# ... and blue.
> sm on ui:blue btnr`short sm!set ui:red
> sm on ui:blue btng`short sm!set ui:green
> sm on ui:blue btnb`short sm!set ui:main
# The rotary encoder should turn off the LEDs in main state.
> sm on ui:main rotenc`left red!off
> sm on ui:main rotenc`left green!off
> sm on ui:main rotenc`left blue!off
# And the rotary right event hould restore the saved PWM state on all channels.
> sm on ui:main rotenc`right red!restore
> sm on ui:main rotenc`right green!restore
> sm on ui:main rotenc`right blue!restore
# So that the user knows which state is active, the LED on the relevant
# button should be activated. I.e. turn it on when entering the state,
# and turn it off when leaving the state.
# (This is not depicted in diagram.)
> event -a ui:red`enter swledr!on
> event -a ui:red`exit swledr!off
> event -a ui:green`enter swledg!on
> event -a ui:green`exit swledg!off
> event -a ui:blue`enter swledb!on
> event -a ui:blue`exit swledb!off
# Switching to a different color should fade over. Therefore, set the
# dimmer step interval to 20ms.
> config set dim_step 20
# The user should also be able to see different things on the dispaly.
# Therefore, a short click on the rotary encoder should switch the
# display to the next available mode.
> event -a rotenc`short display!set_mode
# A medium click on the rotary encoder should display the time.
> event -a rotenc`med display!set_mode time
# To be able to store and restore a specific color setting, a
# medium click on a color button should backup the current PWM value.
> event -a btnr`med red!backup
> event -a btng`med green!backup
> event -a btnb`med blue!backup
# To have some feedback that the medium click invoked the backup, we
# additionally set the application LED to blink twice in that situation.
> event -a btnr`med led!set appled:twice
> event -a btng`med led!set appled:twice
> event -a btnb`med led!set appled:twice
# After everything is configure, save the configuation...
> config write
# ... and reboot the device to activate the configuation.
> reboot
After configuring and rebooting the devcie, you can use action -l
to
list all available actions, and event -l
to list all events an the
actions that are associated with the events. If an action is tagged as
[disabled]
, then this is due to this action only being active in a
specific state of a state-machine that is currently not active.
You can list the configured actions with action -l
which will output for
the above configuration following listing:
all_dimmers!backup backup dimmer and fade off
all_dimmers!off backup dimmer and fade off
all_dimmers!restore restore dimmer from backup
appled!off led off
appled!on led on
appled!toggle toggle led
blue!backup backup dimmer value
blue!dec decrement dimmer value
blue!inc increment dimmer value
blue!off turn off with PWM ramp
blue!on turn on with PWM ramp
blue!restore restore dimmer value
bme280!sample BMP280 sample data
dim!set set dimmer(s) <d> to value <v>: arg = [<d>:]<v>
display!off clock: turn off
display!on clock: turn on
display!set_bright set brightness to argument value
display!set_mode switch to next or specified display mode
gpio!set set gpio <name>:<value>, with value=0,1,z,i,o,t
green!backup backup dimmer value
green!dec decrement dimmer value
green!inc increment dimmer value
green!off turn off with PWM ramp
green!on turn on with PWM ramp
green!restore restore dimmer value
hss!off led off
hss!on led on
hss!toggle toggle led
influx!init init influx connection
influx!rtdata send runtime data
influx!sysinfo send system info
influx!term term influx connection
led!set set LED mode (on,off,slow,fast,once,twice)
lua!run run argument as Lua script
mqtt!pub_rtdata mqtt publish data
mqtt!start mqtt start
mqtt!stop mqtt stop
red!backup backup dimmer value
red!dec decrement dimmer value
red!inc increment dimmer value
red!off turn off with PWM ramp
red!on turn on with PWM ramp
red!restore restore dimmer value
sm!next set next state of state-machine <machine>
sm!set set state of state-machine to <machine>:<state>
statusled!btnpress bind to button press event to monitor with status LED
statusled!btnrel bind to button release event to monitor with status LED
sw!pause stopwatch pause/resume
sw!reset stopwatch reset
sw!startstop stopwatch start/stop
swledb!off led off
swledb!on led on
swledb!toggle toggle led
swledg!off led off
swledg!on led on
swledg!toggle toggle led
swledr!off led off
swledr!on led on
swledr!toggle toggle led
syslog!send trigger sendind dmesg to syslog
timer!disable disable 'at' execution
timer!enable enable 'at' execution
timer!toggle toggle 'at' execution
webtmr!start start this timefuse
webtmr!stop stop this timefuse
wps!start start WPS
You can list the configured events with event -l
which will output for
the above configuration following listing:
init`done (1x, 248ns) =>
swledr!off ()
swledg!off ()
swledb!off ()
hss!on ()
wifi`station_up (3x, 6827ns) =>
udns!init_mdns ()
mqtt!start ()
syslog!start ()
wifi`station_down (0x, 0ns) =>
udns!wifi_down ()
syslog`msg (27x, 20292ns) =>
syslog!send ()
action`trigger (0x, 0ns) =>
action!execute ()
rotenc`released (0x, 0ns) =>
rotenc`pressed (0x, 0ns) =>
rotenc`short (0x, 0ns) =>
display!set_mode ()
rotenc`med (0x, 0ns) =>
display!set_mode (time)
rotenc`left (41x, 908ns) =>
red!off ()
green!off ()
blue!off ()
red!dec () [disabled]
green!dec () [disabled]
blue!dec () [disabled]
rotenc`right (196x, 4190ns) =>
red!restore ()
green!restore ()
blue!restore ()
red!inc () [disabled]
green!inc () [disabled]
blue!inc () [disabled]
btnr`released (3x, 192ns) =>
btnr!up ()
btnr`pressed (4x, 168ns) =>
btnr!down ()
btnr`short (2x, 236ns) =>
sm!set (ui)
sm!set (ui) [disabled]
sm!set (ui:red) [disabled]
sm!set (ui:red) [disabled]
btnr`med (0x, 0ns) =>
led!set (appled:twice)
red!backup ()
btnr`long (0x, 0ns) =>
btng`released (2x, 169ns) =>
btng!up ()
btng`pressed (2x, 116ns) =>
btng!down ()
btng`short (2x, 293ns) =>
sm!set (ui)
sm!set (ui:green) [disabled]
sm!set (ui) [disabled]
sm!set (ui:green) [disabled]
btng`med (0x, 0ns) =>
led!set (appled:twice)
green!backup ()
btng`long (0x, 0ns) =>
btnb`released (2x, 121ns) =>
btnb!up ()
btnb`pressed (2x, 115ns) =>
btnb!down ()
btnb`short (2x, 183ns) =>
sm!set (ui)
sm!set (ui:blue) [disabled]
sm!set (ui:blue) [disabled]
sm!set (ui) [disabled]
btnb`med (0x, 0ns) =>
led!set (appled:twice)
blue!backup ()
btnb`long (0x, 0ns) =>
mqtt`update (0x, 0ns) =>
system`procwifi (0x, 0ns) =>
system!procwifi ()
webtmr`started (1x, 0ns) =>
webtmr`stopped (0x, 0ns) =>
webtmr`timeout (688x, 46us) =>
bme280!sample ()
ui:main`enter (4x, 0ns) =>
ui:main`exit (3x, 0ns) =>
ui:red`enter (1x, 211ns) =>
swledr!on ()
display!set_mode (red)
ui:red`exit (1x, 34ns) =>
swledr!off ()
display!set_mode (time)
ui:green`enter (1x, 182ns) =>
swledg!on ()
display!set_mode (green)
ui:green`exit (1x, 52ns) =>
swledg!off ()
display!set_mode (time)
ui:blue`enter (1x, 130ns) =>
swledb!on ()
display!set_mode (blue)
ui:blue`exit (1x, 38ns) =>
swledb!off ()
display!set_mode (time)
amblight`high (1x, 237ns) =>
dim!set (0)
amblight`low (0x, 0ns) =>
red!restore ()
green!restore ()
blue!restore ()
Any action of an Atirum device can remotely be triggered via MQTT, using the
<nodename>/action
topic. I.e. just publish the action name and its arguments
seperated by a space to the action
topic of the relevant device, and the
device will perform the action if it is connected to the same MQTT server.
Similarly Lua scripts can be called remotely using MQTT. But for Lua scripts it
is recommended to place larger scripts on the device and add them to the list
of Lua files that should be compiled after booting (i.e. config luafiles
).
These scrips then can be remotely be called by their name using MQTT.