Created
August 31, 2025 03:17
-
-
Save sjlongland/a277159d5dec468201cbf711c2e4f97a to your computer and use it in GitHub Desktop.
Controlling a 240V battery charger with a TS-7670 MODBUS 24V switch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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