Skip to content

Instantly share code, notes, and snippets.

@sjlongland
Created August 31, 2025 03:17
Show Gist options
  • Save sjlongland/a277159d5dec468201cbf711c2e4f97a to your computer and use it in GitHub Desktop.
Save sjlongland/a277159d5dec468201cbf711c2e4f97a to your computer and use it in GitHub Desktop.
Controlling a 240V battery charger with a TS-7670 MODBUS 24V switch
#!/bin/sh
# Controlling the 24V power MOSFETs in the TS-7670
# for 240V solid-state relay control.
#
# © 2025 Stuart Longland VK4MSL
# GPIOs for the various control lines.
# References:
# - https://cdn.embeddedts.com/resource-attachments/ts-7670-schematic.pdf
# - https://github.com/vrtsystems/linux/blob/12f2b9e7ba1014341ea3661939ba220b9d3e05f4/arch/arm/boot/dts/imx28-pinfunc.h
GPIO_3V3=47
GPIO_FAULT=46
GPIO_24V=45
# Battery sensor and thresholds in µV
BATT_VOLTAGE=/sys/class/power_supply/ts-psu/voltage_now
BATT_HIGH=14200000
BATT_LOW=11800000
# To prevent wearing out flash, mount ramfs on /mnt/pwrstatus!
BATT_CHARGED_FILE=/mnt/pwrstatus/fullcharge
BATT_CHARGED_DURATION=300
# The paths to the various sysfs interfaces for GPIO
SYSFS_GPIO_BASE=/sys/class/gpio
SYSFS_GPIO_EXPORT=${SYSFS_GPIO_BASE}/export
SYSFS_GPIO_UNEXPORT=${SYSFS_GPIO_BASE}/unexport
SYSFS_GPIO_3V3_BASE=/sys/class/gpio/gpio${GPIO_3V3}
SYSFS_GPIO_FAULT_BASE=/sys/class/gpio/gpio${GPIO_FAULT}
SYSFS_GPIO_24V_BASE=/sys/class/gpio/gpio${GPIO_24V}
# Initialise GPIOs if not already done
gpio_init() {
for gpio in \
${GPIO_3V3} \
${GPIO_FAULT} \
${GPIO_24V} ; \
do
if [ ! -d ${SYSFS_GPIO_BASE}/gpio${gpio} ]; then
echo -n ${gpio} > ${SYSFS_GPIO_EXPORT}
case "${gpio}" in
${GPIO_3V3}) echo -n out > ${SYSFS_GPIO_3V3_BASE}/direction
echo -n 1 > ${SYSFS_GPIO_3V3_BASE}/value
;;
${GPIO_24V}) echo -n out > ${SYSFS_GPIO_24V_BASE}/direction
echo -n 0 > ${SYSFS_GPIO_24V_BASE}/value
;;
${GPIO_FAULT}) echo -n in > ${SYSFS_GPIO_FAULT_BASE}/direction
;;
esac
fi
done
}
# Read the status of the GPIO pins
read_status() {
if [ "$( cat "${SYSFS_GPIO_FAULT_BASE}/value" )" != 0 ]; then
echo fault
elif [ "$( cat "${SYSFS_GPIO_24V_BASE}/value" )" != 0 ]; then
echo on
else
echo off
fi
}
# Power everything off
power_off() {
echo -n 0 > ${SYSFS_GPIO_24V_BASE}/value
echo -n 1 > ${SYSFS_GPIO_3V3_BASE}/value
}
# Safely power on
power_on() {
case "$( read_status )" in
on) return 0
;;
fault)
return 1
;;
esac
echo -n 0 > ${SYSFS_GPIO_3V3_BASE}/value
sleep 0.1
case "$( read_status )" in
on) return 0
;;
fault)
power_off
return 1
;;
esac
echo -n 1 > ${SYSFS_GPIO_24V_BASE}/value
sleep 1
case "$( read_status )" in
on) return 0
;;
*)
power_off
return 1
;;
esac
}
# Read the raw battery voltage in µV
read_voltage_raw() {
cat ${BATT_VOLTAGE}
}
# Scale the battery voltage to volts with 3 decimals
scale_voltage() {
mvolts=$(( ${1} / 1000 ))
volts=$(( ${mvolts} / 1000 ))
mvolts=$(( ${mvolts} % 1000 ))
printf "%d.%03d\n" ${volts} ${mvolts}
}
# Report the battery voltage, scaled to volts
read_voltage_scaled() {
scale_voltage $( read_voltage_raw )
}
# Return the current time (Unix time)
now() {
date +%s
}
# Read the time the battery reached charged state
read_charged_time() {
if [ -f "${BATT_CHARGED_FILE}" ]; then
cat "${BATT_CHARGED_FILE}"
else
echo 0
fi
}
# Mark the battery as charged now
mark_charged_time() {
now > "${BATT_CHARGED_FILE}"
}
# Unmark the battery as being charged
unmark_charged_time() {
rm -f "${BATT_CHARGED_FILE}"
}
# Decide whether mains should be on or not
power_switch_auto() {
v=$( read_voltage_raw )
if [ ${v} -gt ${BATT_HIGH} ]; then
batt_charged=$( read_charged_time )
if [ "${batt_charged}" = 0 ]; then
# Just reached charge
mark_charged_time
echo "Battery charged, waiting ${BATT_CHARGED_DURATION} seconds"
scale_voltage ${v}
return 0
else
# Been on long enough.
case "$( read_status )" in
on)
echo "Battery charged, turning mains off"
scale_voltage ${v}
power_off
;;
fault)
echo "24V bus fault!!!"
power_off
;;
esac
fi
elif [ ${v} -lt ${BATT_LOW} ]; then
unmark_charged_time
case "$( read_status )" in
off)
echo "Battery low, turning mains on"
scale_voltage ${v}
power_on
;;
fault)
echo "24V bus fault!!!"
power_off
;;
esac
fi
}
# Initialise GPIOs
gpio_init
# Process the command-line argument
case "$1" in
on) power_on
exit $?
;;
off)
power_off
exit $?
;;
auto)
power_switch_auto
exit $?
;;
*) read_status
read_voltage_scaled
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment