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.txtin the/bootpartition: - Add at the end:
[all]dtparam=i2c_arm=on
- Modify the
- Install pre-requisites:
sudo apt-get updatesudo apt-get upgradesudo 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
PLDdetects if the A/C power is available or not. If it reads1A/C power was lost.0if A/C power is available. Buzzerif set to1it will sound a rather loud beep.0for off.Buttonsimulates pressing the hardwareOffbutton. If you setButtonto1for 6 seconds, the system will poweroff.Shutdownis used to read the status of the hardware button. This works only ifBootis set to1. Otherwise,Shutdowndoesn't seem to work. WhenBootis set to1, it would read1if pressed,0if released. Weirdly enough, theShutdownbutton 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:gpioSHUTDOWNgpioPLD
gpio[PIN] {1|0}to write i.e.:gpioBOOT 1gpioBUZER 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-1in read/write mode. ioctl(fd, I2C_SLAVE, I2C_ADDRESS)whereI2C_ADDRESS = 0x36.- From
smbus.creadi2c_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:
gensquashfssqfs2tar
And dependant shared libraries (that are not part of the Home Assistant OS image:
liblz4.so.1liblz4.so.1.9.3liblzma.so.5liblzma.so.5.2.5liblzo2.so.2liblzo2.so.2.0.0libselinux.so.1libsquashfs.so.1libsquashfs.so.1.1.0libzstd.so.1libzstd.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/gensquashfsbin/sqfs2tarlib/liblz4.so.1lib/liblz4.so.1.9.3lib/liblzma.so.5lib/liblzma.so.5.2.5lib/liblzo2.so.2lib/liblzo2.so.2.0.0lib/libselinux.so.1lib/libsquashfs.so.1lib/libsquashfs.so.1.1.0lib/libzstd.so.1lib/libzstd.so.1.4.8
Actual X728 support scripts.
bin/x728battetc/x728/clock.shetc/x728/upsmon.shetc/systemd/system/x728clock.serviceetc/systemd/system/x728ups.serviceetc/systemd/system/sysinit.target.wants/x728clock.serviceetc/systemd/system/multi-user.target.wants/x728ups.servicelib/systemd/system-shutdown/gpio-poweroff
Munin node:
bin/munin-nodeetc/systemd/system/sockets.target.wants/munin-node.socketetc/systemd/system/munin-node.socketetc/systemd/system/[email protected]etc/muninlite.conf