241 lines
9.9 KiB
Bash
Executable File
241 lines
9.9 KiB
Bash
Executable File
#!/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"
|