Files
simd/shell/send_sms_gsm.sh
2026-10-25 07:32:43 +03:00

241 lines
9.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# send_sms_gsm_test2.sh — PDU GSM 7-bit, cat-ридер
PORT="/dev/ttyUSB2"
LOG="/pkg/gnu/unitprint/simd/var/modem_sms.log"
SMSC="+79585370690"
# ─── Обработка параметров командной строки ─────────
if [ -z "$1" ]; then
echo "Ошибка: Не указан номер получателя."
echo "Использование: $0 <НОМЕР> [ТЕКСТ]"
echo "Пример: $0 +7xxxxxxxxxx \"OTP 412385\""
exit 1
fi
NUMBER="$1"
# Генерируем случайное 6-значное число (от 000000 до 999999)
RAND_NUM=$(printf "%06d" $((RANDOM % 1000000)))
TEXT="${2:-OTP $RAND_NUM}" # ← Если $2 не задан, берем "OTP <случайное_число>"
# ─── Функция: номер → swapped nibbles ─────────────
format_number() {
local num="${1#+}"
(( ${#num} % 2 != 0 )) && num="${num}F"
local out="" i
for (( i=0; i<${#num}; i+=2 )); do
out+="${num:i+1:1}${num:i:1}"
done
echo "$out"
}
# ─── GSM 7-bit: ASCII → септеты → packed HEX ──────
# Результат в глобальной TEXT_PACKED (не через echo/субшелл)
declare -a SEPTETS
TEXT_PACKED=""
build_gsm7() {
local text="$1" i code
SEPTETS=()
for (( i=0; i<${#text}; i++ )); do
printf -v code '%d' "'${text:i:1}"
case $code in
64) SEPTETS+=(0) ;;
36) SEPTETS+=(2) ;;
10) SEPTETS+=(10) ;;
13) SEPTETS+=(13) ;;
95) SEPTETS+=(17) ;;
*) SEPTETS+=($code) ;;
esac
done
local n=${#SEPTETS[@]}
local oct_count=$(( (n * 7 + 7) / 8 ))
local -a oct
for (( i=0; i<oct_count; i++ )); do oct[i]=0; done
local s v bp bi bo
for (( s=0; s<n; s++ )); do
v=${SEPTETS[$s]}
bp=$(( s * 7 ))
bi=$(( bp / 8 ))
bo=$(( bp % 8 ))
oct[$bi]=$(( (oct[$bi] | ((v & 0x7F) << bo)) & 0xFF ))
if (( bo > 1 )) && (( bi+1 < oct_count )); then
oct[$((bi+1))]=$(( oct[$((bi+1))] | (v >> (8 - bo)) ))
fi
done
TEXT_PACKED=""
for (( i=0; i<oct_count; i++ )); do
TEXT_PACKED+="$(printf '%02X' $(( oct[i] & 0xFF )))"
done
}
# ─── Сборка PDU ДО запуска cat ─────────────────────
SMSC_FMT=$(format_number "$SMSC")
SMSC_DIGITS="${SMSC#+}"
SMSC_LEN=$(printf "%02X" $(( (${#SMSC_DIGITS}+1)/2 + 1 )))
SMSC_PART="${SMSC_LEN}91${SMSC_FMT}"
DEST_DIGITS="${NUMBER#+}"
DEST_FMT=$(format_number "$NUMBER")
DEST_LEN=$(printf "%02X" ${#DEST_DIGITS})
DEST_PART="${DEST_LEN}91${DEST_FMT}"
build_gsm7 "$TEXT"
UDL=${#SEPTETS[@]}
UDL_HEX=$(printf "%02X" $UDL)
PDU_DOCS_FORMAT='
# ==============================================================================
# GSM SMS PDU FORMAT DOCUMENTATION (Context for AI/LLM & Script Maintenance)
# ==============================================================================
# 1. БАЗОВЫЕ ПРАВИЛА (BASIC RULES):
# - PDU представляет собой непрерывную HEX-строку (без пробелов).
# - 2 HEX-символа = 1 Байт (Byte) = 8 Бит (Bits).
# - Структура делится на две части: SMSC_PART + TPDU_PART.
# - Номера телефонов передаются в "Swapped Nibbles" (полубайтовый обмен).
#
# 2. МАКРО-СТРУКТУРА (MACRO STRUCTURE):
# [ SMSC_PART ] + [ TPDU_PART ]
# SMSC_PART: [SMSC_LEN] [SMSC_TOA] [SMSC_NUMBER]
# TPDU_PART: [FO] [MR] [DA_LEN] [DA_TOA] [DA_NUMBER] [PID] [DCS] [UDL] [UD]
#
# 3. ПОБИТОВОЕ РАЗБИЕНИЕ ПОЛЕЙ (BIT-LEVEL FIELD BREAKDOWN):
#
# [FO] First Octet (1 Byte) - Управляет типом и поведением сообщения
# HEX: 01 (Бинарно: 0000 0001)
# Биты: [TP-RP][TP-UDHI][TP-SRR][TP-VPF(2)][TP-RD][TP-MTI(2)]
# Индекс: 7 6 5 4 3 2 1 0
# - Bits 0-1 (TP-MTI): Тип сообщения. 01 = SMS-SUBMIT (Отправка), 00 = SMS-DELIVER
# - Bit 2 (TP-RD): Запрет дубликатов. 0 = Нет, 1 = Да
# - Bits 3-4 (TP-VPF): Формат срока жизни. 00 = Отсутствует, 10 = Относительный
# - Bit 5 (TP-SRR): Запрос отчета о доставке. 0 = Нет, 1 = Да (если 1, то FO=21)
# - Bit 6 (TP-UDHI): Заголовок пользовательских данных. 0 = Нет, 1 = Да (для длинных SMS)
# - Bit 7 (TP-RP): Путь для ответа. 0 = Нет, 1 = Да
#
# [MR] Message Reference (1 Byte) - Reference ID. При отправке всегда "00".
#
# [DA] Destination Address (Переменная длина) - Номер получателя
# - [DA_LEN] (1 Byte): Количество ЦИФР в номере (не байт!). Пример: 11 цифр -> HEX "0B"
# - [DA_TOA] (1 Byte): Тип номера. "91" = Международный (с +), "81" = Национальный
# - [DA_NUM] (N Bytes): Номер в Swapped Nibbles. Если цифр нечетно, дописывается "F".
#
# [PID] Protocol Identifier (1 Byte) - Идентификатор протокола. Обычное SMS = "00".
#
# [DCS] Data Coding Scheme (1 Byte) - Схема кодирования (САМОЕ ВАЖНОЕ ДЛЯ КОДИРОВОК И FLASH)
# Согласно 3GPP TS 23.038 (General Data Coding):
# Биты: [Bit 7-4: Группа] [Bit 3-2: Класс] [Bit 1-0: Алфавит]
# - Bits 0-1 (Алфавит):
# 00 = GSM 7-bit (Латиница/Цифры)
# 01 = 8-bit data
# 10 = UCS2 (Кириллица, UTF-16BE)
# - Bit 4 (Индикатор класса):
# 0 = Класс не указан (По умолчанию Class 3 - обычное сохранение в память)
# 1 = Класс указан (Берется из Bits 2-3)
# - Bits 2-3 (Класс сообщения, если Bit 4 = 1):
# 00 = Class 0 (FLASH SMS / Пиковое уведомление, не сохраняется)
# 01 = Class 1 (Сохранить в телефон)
# 10 = Class 2 (Сохранить на SIM)
# 11 = Class 3 (Сохранить в терминал)
#
# === ШПАРГАЛКА DCS (HEX) ===
# "00" -> 0000 0000 -> GSM 7-bit, Обычное SMS
# "08" -> 0000 1000 -> UCS2, Обычное SMS (Кириллица)
# "10" -> 0001 0000 -> GSM 7-bit, FLASH SMS (Class 0)
# "18" -> 0001 1000 -> UCS2, FLASH SMS (Class 0, Кириллица)
#
# [UDL] User Data Length (1 Byte) - Длина текста
# - Если DCS = 7-bit (00/10): UDL = количество 7-битных символов (septets).
# - Если DCS = UCS2 (08/18): UDL = количество байт (octets).
#
# [UD] User Data (Переменная длина) - Сам текст
# - Для 7-bit: Результат алгоритма "septet packing" (упаковка 7 бит в 8 бит).
# - Для UCS2: Прямой HEX-эквивалент UTF-16BE.
#
# 4. ПРИМЕР СБОРКИ В ЭТОМ СКРИПТЕ (INSTANCE MAPPING):
# TPDU="0100${DEST_PART}0000${UDL_HEX}${TEXT_PACKED}"
# Разбор по байтам:
# 01 -> FO (SMS-SUBMIT)
# 00 -> MR (Reference 0)
# ${DEST} -> DA (Длина + Тип + Номер)
# 00 -> PID (Обычный протокол)
# 00 -> DCS (GSM 7-bit, Обычное SMS)
# ${UDL} -> UDL (Длина в септетах)
# ${TEXT} -> UD (Упакованный HEX текст)
'
# Standart message, GSM 7-bit
TPDU="0100${DEST_PART}0000${UDL_HEX}${TEXT_PACKED}"
# GSM 7-bit, message class=Flash
#TPDU="0100${DEST_PART}0010${UDL_HEX}${TEXT_PACKED}"
TPDU_LEN=$(( ${#TPDU} / 2 ))
FULL_PDU="${SMSC_PART}${TPDU}"
# ─── Проверка что текст на месте ───────────────────
if [[ -z "$TEXT_PACKED" ]]; then
echo "FATAL: TEXT_PACKED пустой!"
exit 1
fi
echo "PDU: $FULL_PDU (${TPDU_LEN} байт)"
echo "Текст: $TEXT → $TEXT_PACKED (${UDL} септетов)"
# ─── Функция ожидания строки в логе ────────────────
wait_in_log() {
local timeout_ms="$1" pattern="$2"
local deadline=$(( $(date +%s%N)/1000000 + timeout_ms ))
while (( $(date +%s%N)/1000000 < deadline )); do
if grep -q "$pattern" "$LOG" 2>/dev/null; then
return 0
fi
sleep 0.02
done
return 1
}
# ─── Запуск cat-ридера ─────────────────────────────
> "$LOG"
stty -F "$PORT" 115200 raw -echo 2>/dev/null
cat "$PORT" >> "$LOG" 2>/dev/null &
READER_PID=$!
# ─── Инициализация ─────────────────────────────────
printf 'AT\r' > "$PORT"; wait_in_log 3000 "OK"
printf 'AT+CMEE=2\r' > "$PORT"; wait_in_log 3000 "OK"
printf 'AT+CMGF=0\r' > "$PORT"; wait_in_log 3000 "OK"
# ─── Отправка ──────────────────────────────────────
> "$LOG" # очистка перед CMGS
START=$(date +%s%N)
printf 'AT+CMGS=%d\r' "$TPDU_LEN" > "$PORT"
# Ждём ">" в логе
if ! wait_in_log 5000 ">"; then
echo "ERROR: не получили >"
cat "$LOG"
kill $READER_PID 2>/dev/null
exit 1
fi
printf '%s\x1A' "$FULL_PDU" > "$PORT"
# Ждём +CMGS или ошибку
if wait_in_log 30000 "+CMGS"; then
ELAPSED=$(( ($(date +%s%N) - START) / 1000000 ))
echo "OK: отправлено за ${ELAPSED} мс"
grep "+CMGS" "$LOG"
elif wait_in_log 2000 "CMS ERROR"; then
echo "ERROR от модема:"
grep -a "CMS ERROR" "$LOG"
else
echo "TIMEOUT: нет ответа"
cat "$LOG"
fi
kill $READER_PID 2>/dev/null
rm -f "$LOG"