The last board in the collection, the brain. Its main component is Orange Pi Zero, a cheap Chinese alternative to Raspberry Pi Zero. Its main advantage is onboard Wi-Fi and Ethernet. The motivation for the very existence of this PCB is to hide the too modern layout of the single board computer from view and provide additional peripherals. Since the schematic is so simple, there’s not much to explain.
As seen in the photos, the board holds the Orange Pi by three screws and 2x13 header. It provides the required 5 V from system bus. Speaking of which, there are four 10-pin system bus connectors, just to make the whole thing future-proof. The MOSFET level shifter shifts 5 V to 3.3 V for I2C.
There is also I2C DAC5573 devised for controlling the external analog gauge meters. It is four-channel, but only two of the outputs are routed to external connectors. There are potentiometers RV1 and RV2 for fine-adjusting the gauge meters. Currently one channel is actually used, it displays the CPU usage. Quite a nice touch.
Switch SW3 is used to disconnect the system bus from Orange Pi and connect to external socket instead. It was put to simplify the development process. There is also SW2 switch which acts as programmatic shut down button. There is script which listens on appropriate GPIO pin to trigger system shutdown.
I put two system indicators too. Since I didn’t manage to find nice-looking LEDs, I stayed with miniature light bulbs. They are switched on by default, they go off only when respective GPIO is set as output and then set to
0. This way, when the boot sequence is complete and
octoglowd daemon is running, they are all off.
The Pi has 16 GB MicroSD with Raspbian installed. It is connected to the home network by Wi-Fi. The central application which controls the whole device is
octoglowd, described in detail in the following section. Octoglow daemon is a Java application. To have it run at startup, I use supervisord. It is convenient, because it also tries to restart the app after unexpected crashes. I also created Python script (software/running-proc-gpio-eventlistenter.py) to drive the light bulb associated with running daemon. Supervisor monitors the daemon, when it crashes, the light goes up automatically.
The other script software/reset-button-handler.sh constantly monitors the state of the button GPIO. If it detects state
0, it calls
shutdown -h now. This is done to safely shut down the Linux system without the need to log into it via SSH.
octoglowd (software/octoglowd) is a JVM application written in Kotlin. I made heavy use of coroutines, the Kotlin approach to concurrent programming. It provided me the great opportunity to learn them. I used some notable libraries:
- dvlopt I2C library for accessing I2C devices
- exposed as ORM backed with sqlite3
- stateful4k - state machine framework (not available in Maven central repository, you have to install it yourself)
- konf for managing the configuration. At startup, it is loaded from config.yml
It should be noted that this app is an second attempt to write Octoglow daemon. The previous one was done in Rust, but I suffered from steep learning curve and immature language. Lacking good async libraries also didn’t help. This abandoned code is archived in branch
octoglowd-rust in the repository.
The architecture is all about standalone workers called daemons, which take care of all aspects of the functionality. Each daemon defines its pooling interval. Some more important daemons:
RealTimeClockDaemon- called each second, sets the current time on the clock display
CpuUsageIndicatorDaemon- periodically checks the CPU usage and sets the gauge needle
FrontDisplayDaemon- most important daemon, pools the front display’s dial and draws the content of the front display.
There is naturally code for communicating with each I2C device. I made an abstraction class
I2CDevice, which wraps dvlopt library in coroutines nicely.
Front display daemon
FrontDisplayDaemon is the most complicated daemon because it accepts input from the dial and manages the pooling data from various devices and redraws the display. It is based on the concept of views. Each view is concerned with collecting data from single sensor or service and displaying it on the screen. The user cycles between the views by turning the knob. If there’s no input from the user for a prolonged time, the views are cycled automatically, by predefined interval. Each view can also define its own menu. You enter it by pressing the knob. For example,
GeigerView has menu to enable or disable the magic eye.
Let’s describe the functionality taking
CryptocurrencyView as example. Its task is to download exchange rates of three defined coins from coinpaprika.com and display them on the screen. Function
poolStatusData() interacts with coinpaprika API and saves the result to the database. Function
redrawDisplay() draws the data on the screen. Each exchange rate has its differential chart drawn to easily spot the trend. The differential chart functionality is used in many views. The resolution of the VFD display is limiting the amount of information which can be shown at once, but it does the trick nonetheless.
Some of the views which are implemented are shown below: