X728 kit for Raspberry Pi 4
As part of my small project of movng my Z-Wave Hub to a Raspberry PI, I got an X728 kit. This has:
- UPS controller board
- RTC circuit
- Battery and Power control board
- Case
- Button
- Cooling fan
- Additional Battery holder
The case has holes for wall-mounting.
The Geekworm X728 kit is very easy to build. There is a video to show how to do this:
Otherwise, refer to the hardware guide here.
In my case, before the build, I took the disassembled case to measure the holes needed for wall-mounting the case. You need fairly small screws for this. I actually had to bend the case slighly for my screws to work.
Also, I set the jumper to automatic Power-on and a few cable ties to fix a USB Hub to the case.
To test the hardware I downloaded a 64-bit Raspberry OS Lite image from Raspberrypi.com and image an micro-SD card.
- Boot the Raspberry OS. The first boot will resize the filesystems, so please wait. Also, it will let you configure the default user and password.
- Enable the i2c function:
sudo raspi-config
- Go to
Interfacing Options
->I2C - Enable/Disable automatic loading
. - While you are at-it, you may also enable
SSH
.
- Alternatively, you can a manual install by:
- Modify the
config.txt
in the/boot
partition: - Add at the end:
[all]
dtparam=i2c_arm=on
- Modify the
- Install pre-requisites:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get -y install i2c-tools
- This is only needed for
i2cdetect
.
- Reboot the system.
- Check if the hardware is detected:
sudo i2cdetect -y 1
#36
- the address of the battery fuel gauging chip#68
- the address of the RTC chip- Different x728 versions may have different values. Mine used these values.
I personally did not like the example software. This can be found in github.
Specifically, the shutdown
functionality seemed to have race-conditions.
However, using it is not that complicated. So I wrote my own software, but you can do your own thing:
RTC functionality
The RTC functionality is supported by the Raspberry OS kernel. You need to enable
the i2c
functionality in /boot/config.txt
by adding the line:
dtparam=i2c_arm=on
With that enabled, you need to add the kernel modules:
i2c-dev
rtc-ds1307
You need to enable it in the bus:
echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device
From then on, you can use the standard hwclock
command.
These can be added to rc.local
which is how the sample code does. I prefer to
do this from a script run from systemd
unit file:
# file: /etc/systemd/system/x728clock.service
[Unit]
Description=Restore / save X728 clock
DefaultDependencies=no
Before=sysinit.target shutdown.target
Conflicts=shutdown.target
[Service]
ExecStart=/etc/x728/clock.sh start
ExecStop=/etc/x728/clock.sh stop
Type=oneshot
RemainAfterExit=yes
[Install]
WantedBy=sysinit.target
After saving this file and creating a /etc/x728/clock.sh
script you can:
systemctl daemon-reload
systemctl enable x728clock
systemctl start x728clock
systemctl disable fake-hwclock
systemctl stop fake-hwclock
GPIO assignments
Pin | Function | Direction | Comment |
---|---|---|---|
#6 | PLD | in | 1: A/C lost, 0: A/C OK |
#5 | 5hutdown | in | Sense button press |
#12 | Boot | out | Control SW/HW controlled button |
#20 | Buzzer | out | |
#26 | Button | out | Simulate power button press |
- Reading
PLD
detects if the A/C power is available or not. If it reads1
A/C power was lost.0
if A/C power is available. Buzzer
if set to1
it will sound a rather loud beep.0
for off.Button
simulates pressing the hardwareOff
button. If you setButton
to1
for 6 seconds, the system will poweroff.Shutdown
is used to read the status of the hardware button. This works only ifBoot
is set to1
. Otherwise,Shutdown
doesn't seem to work. WhenBoot
is set to1
, it would read1
if pressed,0
if released. Weirdly enough, theShutdown
button is not very sensitive. It takes about 3 seconds to register the button press. The button release takes bout 50 seconds to detect.
GPIO programming
Programming GPIO is quite easy. It can be done from shell scripting using the sys file-system.
gpioIO() {
local pin=$1
if [ $# -eq 1 ] ; then
cat /sys/class/gpio/gpio$pin/value
else
echo "$2" > /sys/class/gpio/gpio$pin/value
fi
}
gpioInit() {
local name="$1" pin="$2" dir="$3"
[ ! -d /sys/class/gpio/gpio$pin ] && echo "$pin" > /sys/class/gpio/export
echo $dir > /sys/class/gpio/gpio$pin/direction
eval "gpio${name}() { gpioIO $pin \"\$@\" ; }"
}
ticks() {
echo $(date +%s)$(date +%N | cut -c-2)
}
beep() {
local len="$1" ; shift
gpioBUZZER 1
sleep "$len"
gpioBUZZER 0
[ $# -eq 0 ] && return
local repeat="$1" idle
[ $# -gt 1 ] && idle="$2" || idle="$len"
while [ $repeat -gt 1 ]
do
repeat=$(expr $repeat - 1)
sleep "$idle"
gpioBUZZER 1
sleep "$len"
gpioBUZZER 0
done
}
gpioInit SHUTDOWN 5 in
gpioInit PLD 6 in
gpioInit BOOT 12 out
gpioInit BUZZER 20 out
gpioInit BUTTON 26 out
Afterwards, you can just:
gpio[PIN]
to read, i.e:gpioSHUTDOWN
gpioPLD
gpio[PIN] {1|0}
to write i.e.:gpioBOOT 1
gpioBUZER 0
Reading Battery status
You can read battery voltage and battery charge from the smbus
. To read the smbus
I am using an example from [rpi-examples]((https://github.com/leon-anavi/rpi-examples/tree/master/BMP180/c).
Specifically, I am only using the files smbus.c
and smbus.h
from that repository.
The code outline is:
- open
/dev/i2c-1
in read/write mode. ioctl(fd, I2C_SLAVE, I2C_ADDRESS)
whereI2C_ADDRESS = 0x36
.- From
smbus.c
readi2c_smbus_read_word_data(fd, address)
, and byte swap. - Voltage can be read from address
2
:Voltage = (swapped) * 1.25 / 1000 / 16
- Battery charge can be read from address
4
:Battery = (swapped) / 256
The code to do this can be found on github.
A precompiled 64bit static binary can be found there too.
Power down
The x728 will turn off power by holding down the power button for around
6 seconds. Doing this will skip the shutdown
process. Also, if you execute the
poweroff
command, the Raspberry Pi will shutdown but power will not go OFF until
you hold the power button for 6 seconds.
For this to work properly, I am adding this small script to /lib/systemd/system-shutdown/gpio-poweroff
:
#!/bin/sh
#
# file: /lib/systemd/system-shutdown/gpio-poweroff
# $1 will be either "halt", "poweroff", "reboot" or "kexec"
#
BUTTON=26
op_poweroff() {
echo $BUTTON > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio$BUTTON/direction
echo 1 > /sys/class/gpio/gpio$BUTTON/value
sync;sync;sync
sleep 7
echo 0 > /sys/class/gpio/gpio$BUTTON/value
sleep 3
}
case "$1" in
poweroff) op_poweroff ;;
esac
This hooks into systemd
's shutdown
target and uses the BUTTON
pin to
simulate holding the button for 6 seconds to force the UPS board to power off.
UPS management
In addition, I wrote a small script to:
- graceful shutdown when power button is pressed.
- hold the power button, after approximately 3 seconds, you will hear 2 beeps. YOu can release the power button then. The system will do a graceful shutdown and power down.
- When A/C power is lost:
- If battery status can not be determined, the system will do a graceful powerdown.
- If battery status can be read, it will beep once every 60 seconds until power is restored.
- If battery is low, it will do a graceful powerdown.
- Events are written to /dev/kmsg, so they could be forwarded to a syslog server.
The files to do this are:
Home Assistant
I am using the X728 kit for creating a Home Assistant installation. Home Assistant has a "managed Operating System" called Home Assistant OS which is a mostly read-only installation. This makes it complicated to add your own "low-level" customizations. To include these scripts and making them persistant accross upgrades I am hooking into the RAUC OTA upgrade subsystem.
For that, I hook up to the System handlers which makes use of a Handler Interface.
With these scripts, I am able to move the customizations from a previous image to the new upgraded image.
For this, I have a script haos-x728
that injects the customizations
into a new installation image. This script also modifies /etc/rauc/system.conf
so that the customization handler is called during an upgrade.
The post-install handler
re-adds the handler to /etc/rauc/system.conf
and copies the necessary files to the updated image.
The customization scripts are not X728 specific, and essentially lets
you copy all the files in a directory to the custom image. As such, I am using
to not only inject these X728 scripts, but also the vcgencmd
and a
muninlite
agent. Also, the dependant binaries for the
post-install handler
are injected in the same way.
For the post-install handler to work properly you need to copy binaries for:
gensquashfs
sqfs2tar
And dependant shared libraries (that are not part of the Home Assistant OS image:
liblz4.so.1
liblz4.so.1.9.3
liblzma.so.5
liblzma.so.5.2.5
liblzo2.so.2
liblzo2.so.2.0.0
libselinux.so.1
libsquashfs.so.1
libsquashfs.so.1.1.0
libzstd.so.1
libzstd.so.1.4.8
The simplest way to get these is to install squashfs-tools-ng
on standard
Raspberry PI OS
and copy those files from there.
The full set of customization files that I am using can be found here.
It contains:
RAUC handler:
lib/rauc/post-install
squashfs-tools-ng
(dependancy to RAUC handler)
bin/gensquashfs
bin/sqfs2tar
lib/liblz4.so.1
lib/liblz4.so.1.9.3
lib/liblzma.so.5
lib/liblzma.so.5.2.5
lib/liblzo2.so.2
lib/liblzo2.so.2.0.0
lib/libselinux.so.1
lib/libsquashfs.so.1
lib/libsquashfs.so.1.1.0
lib/libzstd.so.1
lib/libzstd.so.1.4.8
Actual X728 support scripts.
bin/x728batt
etc/x728/clock.sh
etc/x728/upsmon.sh
etc/systemd/system/x728clock.service
etc/systemd/system/x728ups.service
etc/systemd/system/sysinit.target.wants/x728clock.service
etc/systemd/system/multi-user.target.wants/x728ups.service
lib/systemd/system-shutdown/gpio-poweroff
Munin node:
bin/munin-node
etc/systemd/system/sockets.target.wants/munin-node.socket
etc/systemd/system/munin-node.socket
etc/systemd/system/[email protected]
etc/muninlite.conf