Youri
October 25, 2025, 8:54pm
1
Hey everyone
I’ve been working on a small Go program for my X3000 modem that connects directly to the SIM module’s serial port and communicates through AT commands. It looks for new messages in the incoming SMS directory and automatically sends a reply through the COM port if the message matches a specific regex pattern.
It works about half the time, but the other half it just locks up. I think the GL.iNet core packages are running into a race condition whenever both streams overlap. When that happens, everything starts to glitch, sometimes it fails completely, and sometimes it even takes down my internet connection.
Is there a cleaner or more reliable way to handle AT commands on the client side? Maybe some sort of queue I could send commands through instead of pushing them directly?
admon
October 25, 2025, 9:11pm
2
I would recommend hooking up /etc/forward
Rename it to /etc/forward.real and make sure that you call exec /etc/forward.real "$@" at the end of your script. You might need to wrap a shell script around your go app, I am not sure if you can run it directly.
/etc/forward will be used as soon as you enable forwarding an SMS inside the GL interface. So this script will always be notified about new SMS.
Youri
October 25, 2025, 9:25pm
5
That looks interesting but this cant work dynamically right? As I can’t provide a number to forward it to
admon
October 25, 2025, 9:33pm
6
Since you write your own script, you can do whatever you like - so dynamically is possible of course.
This one is my example; I'm not sure if it still works.
#!/bin/sh
# Wrapper for GL.iNet smstools:
# Detects O2 data usage warning messages and automatically replies "WEITER"
# Then calls the original /etc/forward.real script.
LOG="/var/log/o2_autoweiter.log"
OUTBASE="/etc/spool/sms/outgoing/0001:01:00.0" # adjust if your modem slot path changes
O2_SHORT="80112"
# --- Text recognition patterns ---
# Positive: data usage warning + instruction to reply
RE_REQUIRE='(80%|100%|Highspeed[^\.]*Datenvolumen|Inklusivvolumen)'
RE_WEITER_HINT='(antworte|antworten|mit)[^\.]*WEITER'
# Negative: success or confirmation messages (no further reply)
RE_NEG='(erfolgreich aktiviert|wurde erfolgreich aktiviert|bereits aktiviert|aktiviert\.?\s*Ihr o2 Team)'
# --- State files ---
STAMP="/tmp/o2_weiter_last" # timestamp of last WEITER sent
PENDING="/tmp/o2_weiter_pending" # prevents new WEITER while waiting for confirmation
SEEN_DIR="/tmp/o2_seen"; mkdir -p "$SEEN_DIR"
THROTTLE=60 # seconds between two WEITER messages (minimum)
PENDING_TTL=240 # seconds to keep pending before it expires
log(){ echo "[$(date +'%F %T')] $*" >> "$LOG"; logger -t o2_autoweiter "$*" >/dev/null 2>&1; }
mk_out(){
mkdir -p "$OUTBASE"
tmp="$(mktemp "$OUTBASE"/o2weiter.XXXXXX)" || return 1
{
echo "To: $O2_SHORT"
echo "Alphabet: ISO"
echo
echo "WEITER"
} >"$tmp"
mv "$tmp" "$OUTBASE/weiter-$(date +%s).sms" 2>/dev/null || true
}
# --- Main logic ---
event="$1"; infile="$2"; device="$3"
if [ "$event" = "RECEIVED" ] && [ -n "$infile" ] && [ -f "$infile" ]; then
bname="$(basename "$infile")"
[ -f "$SEEN_DIR/$bname" ] && { log "Already processed ($bname) – skipping."; exec /etc/forward.real "$@"; exit 0; }
# Split header/body at the first empty line
line="$(awk '/^\s*$/{print NR; exit}' "$infile")"
if [ -n "$line" ]; then
header="$(sed -n "1,${line}p" "$infile")"
body="$(sed -n "$((line+1)),\$p" "$infile")"
else
header="$(sed -n '1,20p' "$infile")"
body="$(sed -n '21,$p' "$infile")"
fi
from="$(printf '%s\n' "$header" | sed -n 's/^From:[[:space:]]*\(.*\)$/\1/p' | head -n1)"
[ -z "$from" ] && from="$(printf '%s\n' "$header" | sed -n 's/^Sender:[[:space:]]*\(.*\)$/\1/p' | head -n1)"
preview="$(printf '%s' "$body" | tr '\n' ' ' | sed 's/[[:cntrl:]]//g' | cut -c1-220)"
log "EVENT=$event dev=${device:-?} file=$bname from=${from:-unknown} body='$preview'"
# --- Handle different SMS types ---
# Success / confirmation messages
if printf '%s' "$body" | grep -Eiq "$RE_NEG"; then
[ -f "$PENDING" ] && rm -f "$PENDING"
log "Confirmation SMS detected – no WEITER sent, pending cleared."
# Ignore self-sent "WEITER" echoes
elif printf '%s' "$body" | grep -Eq '^\s*WEITER\s*$'; then
log "Own WEITER detected – ignoring."
# Data limit warnings with 'reply WEITER' instruction
elif printf '%s' "$body" | grep -Eiq "$RE_REQUIRE" && printf '%s' "$body" | grep -Eiq "$RE_WEITER_HINT"; then
now="$(date +%s)"
# Check pending (wait for confirmation)
if [ -f "$PENDING" ]; then
age=$(( now - $(cat "$PENDING" 2>/dev/null || echo 0) ))
if [ "$age" -lt "$PENDING_TTL" ]; then
log "Pending active ($age s) – skipping new WEITER."
touch "$SEEN_DIR/$bname"
exec /etc/forward.real "$@"; exit 0
else
log "Pending expired ($age s) – allowing new WEITER."
rm -f "$PENDING"
fi
fi
# Check throttle
last=0; [ -f "$STAMP" ] && last="$(cat "$STAMP" 2>/dev/null || echo 0)"
if [ $(( now - last )) -lt "$THROTTLE" ]; then
log "Throttle active: last WEITER sent $(( now - last ))s ago – skipping."
else
if mk_out; then
echo "$now" >"$STAMP"; echo "$now" >"$PENDING"
log "→ WEITER added to outgoing (text match)."
else
log "Error: failed to create outgoing message."
fi
fi
fi
touch "$SEEN_DIR/$bname"
fi
# --- Always run the original handler afterwards ---
exec /etc/forward.real "$@"
1 Like