From 538931d35f065a558d3d7ea3728b7fc15f196327 Mon Sep 17 00:00:00 2001 From: TraceLumen Date: Sun, 25 Oct 2026 13:35:24 +0300 Subject: [PATCH] logic and flags update --- shell/send_sms.sh | 359 +++++++++++++++++++++++++++++++++++++++++ shell/send_sms_gsm.sh | 240 --------------------------- shell/send_sms_ucs2.sh | 70 -------- 3 files changed, 359 insertions(+), 310 deletions(-) create mode 100755 shell/send_sms.sh delete mode 100755 shell/send_sms_gsm.sh delete mode 100755 shell/send_sms_ucs2.sh diff --git a/shell/send_sms.sh b/shell/send_sms.sh new file mode 100755 index 0000000..0031fa6 --- /dev/null +++ b/shell/send_sms.sh @@ -0,0 +1,359 @@ +#!/bin/bash +# send_sms.sh — SMS PDU constructor, bitwise fields +# Зависимости: bash, stty, grep, iconv, xxd (для --ucs2) + +set -u + +# ══════════════════════════════════════════════════════════════════ +# ЗНАЧЕНИЯ ПО УМОЛЧАНИЮ +# ══════════════════════════════════════════════════════════════════ +PORT="/dev/ttyUSB2" +SMSC="+79585370690" +LOG="/pkg/gnu/unitprint/simd/var/modem_sms.log" + +ENCODING="gsm" # gsm | ucs2 +CLASS="" # "" = не указан (класс не задан), 0..3 +MR=0 # TP-Message-Reference 0..255 +REPORT=0 # TP-SRR: 1 = запрашивать delivery report +UDHI=0 # TP-UDHI: 1 = в тексте есть UDH (concatenation) +RP=0 # TP-RP: request reply path +RD=0 # TP-RD: reject duplicates +VALIDITY_MIN="" # TP-VP, минуты (относительный формат) +USE_SMSC=1 # 0 = не включать SMSC в PDU (возьмётся из AT+CSCA) +TOA=91 # 91 = international (+), 81 = national +DRY_RUN=0 +RECEIVER="" +TEXT="" + +# ══════════════════════════════════════════════════════════════════ +# USAGE +# ══════════════════════════════════════════════════════════════════ +usage() { +cat <<'EOF' +Использование: + send_sms.sh [ОПЦИИ] <НОМЕР> [ТЕКСТ] + +Если ТЕКСТ не задан — генерируется "OTP <6 случайных цифр>". + +Кодировка (взаимоисключающие): + --gsm GSM 7-bit, по умолчанию (латиница/цифры) + --ucs2 UCS2 / UTF-16BE (кириллица и др.) + --encoding=gsm|ucs2 то же самое + +Класс сообщения (DCS): + --class=0..3 0=Flash, 1=ME, 2=SIM, 3=TE (без явного указания + класс НЕ передаётся → обычный SMS) + --flash синоним --class=0 + +Заголовок TPDU (FO): + --report TP-SRR=1, запрос отчёта о доставке (медленнее!) + --udhi TP-UDHI=1, в тексте есть UDH + --reply-path TP-RP=1 + --reject-duplicates TP-RD=1 + --mr=N TP-MR, 0..255 (по умолчанию 0) + --validity=MIN TP-VP, срок жизни в минутах (до 10080 = 7 суток) + +Адресация / транспорт: + --port=/dev/ttyUSB2 порт модема + --smsc=+7... SMSC (по умолчанию из конфига) + --no-smsc не включать SMSC в PDU (используется AT+CSCA) + --national TOA=81, НОМЕР передавать без '+' + --log=/path файл для сырых ответов модема + +Прочее: + --dry-run напечатать PDU и выйти, ничего не отправлять + -h, --help эта справка +EOF +} + +die() { echo "ERROR: $*" >&2; exit 1; } + +# ══════════════════════════════════════════════════════════════════ +# РАЗБОР АРГУМЕНТОВ +# ══════════════════════════════════════════════════════════════════ +POSITIONAL=() +while (( $# > 0 )); do + case "$1" in + -h|--help) usage; exit 0 ;; + --gsm) ENCODING="gsm" ;; + --ucs2) ENCODING="ucs2" ;; + --encoding=*) ENCODING="${1#*=}" ;; + --encoding) shift; ENCODING="${1:-}" ;; + --class=*) CLASS="${1#*=}" ;; + --class) shift; CLASS="${1:-}" ;; + --flash) CLASS="0" ;; + --mr=*) MR="${1#*=}" ;; + --report) REPORT=1 ;; + --udhi) UDHI=1 ;; + --reply-path) RP=1 ;; + --reject-duplicates) RD=1 ;; + --validity=*) VALIDITY_MIN="${1#*=}" ;; + --port=*) PORT="${1#*=}" ;; + --smsc=*) SMSC="${1#*=}" ;; + --log=*) LOG="${1#*=}" ;; + --no-smsc) USE_SMSC=0 ;; + --national) TOA=81 ;; + --dry-run) DRY_RUN=1 ;; + --*) die "неизвестный флаг: $1 (см. --help)" ;; + *) POSITIONAL+=("$1") ;; + esac + shift +done + +(( ${#POSITIONAL[@]} >= 1 )) || { usage; exit 1; } +RECEIVER="${POSITIONAL[0]}" +RAND_NUM=$(printf '%06d' $(( RANDOM % 1000000 ))) +TEXT="${POSITIONAL[1]:-OTP $RAND_NUM}" + +[[ "$ENCODING" == "gsm" || "$ENCODING" == "ucs2" ]] || die "--encoding: только gsm | ucs2" +[[ "$CLASS" == "" || "$CLASS" =~ ^[0-3]$ ]] || die "--class: только 0..3" +[[ "$MR" =~ ^[0-9]+$ && "$MR" -le 255 ]] || die "--mr: 0..255" +if [[ -n "$VALIDITY_MIN" ]]; then + [[ "$VALIDITY_MIN" =~ ^[0-9]+$ ]] || die "--validity: целые минуты" + (( VALIDITY_MIN >= 1 && VALIDITY_MIN <= 10080 )) || die "--validity: 1..10080 минут" +fi +if [[ "$TOA" == "81" ]]; then + [[ "$RECEIVER" =~ ^[0-9]{5,15}$ ]] || die "--national: номер без '+' и символов" +else + [[ "$RECEIVER" =~ ^\+[0-9]{7,15}$ ]] || die "номер должен быть в формате +7XXXXXXXXXX" +fi + +# ══════════════════════════════════════════════════════════════════ +# BITWISE: TP-DCS (3GPP TS 23.038) +# ══════════════════════════════════════════════════════════════════ +build_dcs() { + local alpha=0 + [[ "$ENCODING" == "ucs2" ]] && alpha=2 # 10b + + local dcs=0 + if [[ -n "$CLASS" ]]; then + dcs=$(( dcs | (1 << 4) )) # bit 4: класс задан + dcs=$(( dcs | (CLASS << 2) )) # bits 3-2: класс + dcs=$(( dcs | alpha )) # bits 1-0: алфавит + else + dcs=$(( dcs | (alpha << 2) )) # bits 3-2: алфавит + fi + printf '%02X' "$dcs" +} + +# ══════════════════════════════════════════════════════════════════ +# BITWISE: TP-FO (3GPP TS 23.040, SMS-SUBMIT) +# ══════════════════════════════════════════════════════════════════ +build_fo() { + local fo=1 # bits 1-0: MTI = 01 + (( RD )) && fo=$(( fo | (1 << 2) )) + [[ -n "$VALIDITY_MIN" ]] && fo=$(( fo | (2 << 3) )) # VPF = 10 + (( REPORT )) && fo=$(( fo | (1 << 5) )) + (( UDHI )) && fo=$(( fo | (1 << 6) )) + (( RP )) && fo=$(( fo | (1 << 7) )) + printf '%02X' "$fo" +} + +# TP-VP relative +build_vp() { + local m="$1" v + if (( m <= 720 )); then v=$(( (m + 4) / 5 - 1 )) + elif (( m <= 1440 )); then v=$(( 143 + (m - 720 + 29) / 30 )) + else v=$(( 166 + (m + 1439) / 1440 )) + fi + (( v < 0 )) && v=0 + (( v > 196 )) && v=196 + printf '%02X' "$v" +} + +# ══════════════════════════════════════════════════════════════════ +# НОМЕР → TON/NPI + swapped nibbles +# ══════════════════════════════════════════════════════════════════ +format_number() { + local num="${1#+}" out="" i + (( ${#num} % 2 )) && num="${num}F" + for (( i=0; i<${#num}; i+=2 )); do + out+="${num:i+1:1}${num:i:1}" + done + printf '%s' "$out" +} + +# Для DA (получатель) длина — это КОЛИЧЕСТВО ЦИФР (без '+') +build_da() { + local num="${1#+}" + local digits=${#num} + local len_hex=$(printf '%02X' "$digits") + local num_hex=$(format_number "$1") + DA_HEX="${len_hex}${2}${num_hex}" +} + +# Для SMSC длина — это КОЛИЧЕСТВО ОКТЕТОВ (включая байт TOA) +build_smsc() { + local num="${1#+}" + local digits=${#num} + local octets=$(( (digits + 1) / 2 + 1 )) + local len_hex=$(printf '%02X' "$octets") + local num_hex=$(format_number "$1") + SMSC_HEX="${len_hex}${2}${num_hex}" +} + +# ══════════════════════════════════════════════════════════════════ +# GSM 7-bit / UCS2 +# ══════════════════════════════════════════════════════════════════ +declare -a SEPTETS +TEXT_HEX="" + +build_gsm7() { + local text="$1" i code ch + SEPTETS=() + for (( i=0; i<${#text}; i++ )); do + ch="${text:i:1}" + printf -v code '%d' "'$ch" + if (( code > 127 )); then + die "символ '$ch' не в GSM 7-bit. Используйте --ucs2" + fi + case $code in + 64) SEPTETS+=(0) ;; # @ + 36) SEPTETS+=(2) ;; # $ + 95) SEPTETS+=(17) ;; # _ + 10) SEPTETS+=(10) ;; + 13) SEPTETS+=(13) ;; + 123|125|91|93|126|92|124|8364) + die "расширенный GSM-алфавит ('$ch') пока не поддержан, используйте --ucs2" ;; + *) SEPTETS+=("$code") ;; + esac + done + + local n=${#SEPTETS[@]} + local oct_count=$(( (n * 7 + 7) / 8 )) + local -a oct=() + for (( i=0; i 1 )) && (( bi + 1 < oct_count )); then + oct[$((bi+1))]=$(( oct[$((bi+1))] | (v >> (8 - bo)) )) + fi + done + + TEXT_HEX="" + for (( i=0; i/dev/null) || die "iconv: неверная UTF-8 строка" + TEXT_HEX=$(printf '%s' "$raw" | xxd -p | tr -d '\n' | tr 'a-f' 'A-F') + UDL=$(( ${#TEXT_HEX} / 2 )) +} + +# ══════════════════════════════════════════════════════════════════ +# СБОРКА PDU +# ══════════════════════════════════════════════════════════════════ +[[ -n "$TEXT" ]] || die "пустой текст" + +if [[ "$ENCODING" == "gsm" ]]; then build_gsm7 "$TEXT"; else build_ucs2 "$TEXT"; fi + +MAX_UDL=160 +[[ "$ENCODING" == "ucs2" ]] && MAX_UDL=70 +(( UDL <= MAX_UDL )) || die "текст длиннее одного SMS ($UDL > $MAX_UDL). Нужна конкатенация/UDH" + +FO=$(build_fo) +DCS=$(build_dcs) +MR_HEX=$(printf '%02X' "$MR") +UDL_HEX=$(printf '%02X' "$UDL") +VP_HEX="" +[[ -n "$VALIDITY_MIN" ]] && VP_HEX=$(build_vp "$VALIDITY_MIN") + +build_da "$RECEIVER" "$TOA" + +SMSC_HEX="" +if (( USE_SMSC )); then + build_smsc "$SMSC" 91 +fi + +# [FO][MR][DA][PID][VP?][DCS][UDL][UD] +TPDU="${FO}${MR_HEX}${DA_HEX}00${VP_HEX}${DCS}${UDL_HEX}${TEXT_HEX}" +TPDU_LEN=$(( ${#TPDU} / 2 )) +FULL_PDU="${SMSC_HEX}${TPDU}" + +echo "FO=$FO MR=$MR_HEX PID=00 DCS=$DCS UDL=$UDL_HEX VP=${VP_HEX:-none}" +echo "CLASS=${CLASS:-не задан} ENCODING=$ENCODING" +echo "ТЕКСТ : $TEXT" +echo "TPDU : $TPDU_LEN байт" +echo "PDU : $FULL_PDU" + +(( DRY_RUN )) && exit 0 + +# ══════════════════════════════════════════════════════════════════ +# ТРАНСПОРТ: cat-ридер + ожидание маркеров +# ══════════════════════════════════════════════════════════════════ +READER_PID="" +cleanup() { + [[ -n "$READER_PID" ]] && kill "$READER_PID" 2>/dev/null + wait "$READER_PID" 2>/dev/null +} +trap cleanup EXIT INT TERM + +mkdir -p "$(dirname "$LOG")" 2>/dev/null +: > "$LOG" + +stty -F "$PORT" 115200 raw -echo 2>/dev/null || true +cat "$PORT" >> "$LOG" 2>/dev/null & +READER_PID=$! + +wait_in_log() { + local timeout_ms="$1" pattern="$2" + local deadline=$(( $(date +%s%N) / 1000000 + timeout_ms )) + while (( $(date +%s%N) / 1000000 < deadline )); do + grep -aqF -- "$pattern" "$LOG" 2>/dev/null && return 0 + sleep 0.01 + done + return 1 +} + +send_wait() { + : > "$LOG" + printf '%s\r' "$1" > "$PORT" + local deadline=$(( $(date +%s%N) / 1000000 + ${2:-3000} )) + while (( $(date +%s%N) / 1000000 < deadline )); do + if grep -aqF "OK" "$LOG" 2>/dev/null; then return 0; fi + if grep -aqF "ERROR" "$LOG" 2>/dev/null; then return 1; fi + sleep 0.01 + done + return 1 +} + +# Инициализация +send_wait 'AT' || die "модем не отвечает (AT)" +send_wait 'AT+CMEE=2' || die "AT+CMEE=2" +send_wait 'AT+CMGF=0' || die "AT+CMGF=0" +(( USE_SMSC )) || send_wait 'AT+CSCA?' || die "AT+CSCA? — SMSC не задан" + +# Отправка +: > "$LOG" +START=$(date +%s%N) +printf 'AT+CMGS=%d\r' "$TPDU_LEN" > "$PORT" + +if ! wait_in_log 5000 '>'; then + echo "ERROR: нет приглашения '>'"; cat "$LOG"; exit 1 +fi + +printf '%s\x1A' "$FULL_PDU" > "$PORT" + +# Ждём либо успех, либо ошибку +DEADLINE=$(( $(date +%s%N) / 1000000 + 30000 )) +RC=2 +while (( $(date +%s%N) / 1000000 < DEADLINE )); do + if grep -aqF '+CMGS:' "$LOG"; then RC=0; break; fi + if grep -aqF 'CMS ERROR' "$LOG"; then RC=1; break; fi + sleep 0.01 +done + +ELAPSED=$(( ($(date +%s%N) - START) / 1000000 )) + +case "$RC" in + 0) echo "OK: отправлено за ${ELAPSED} мс"; grep -a '+CMGS:' "$LOG"; exit 0 ;; + 1) echo "ERROR (${ELAPSED} мс):"; grep -a 'CMS ERROR' "$LOG"; exit 1 ;; + *) echo "TIMEOUT (${ELAPSED} мс)"; cat "$LOG"; exit 2 ;; +esac diff --git a/shell/send_sms_gsm.sh b/shell/send_sms_gsm.sh deleted file mode 100755 index b451b27..0000000 --- a/shell/send_sms_gsm.sh +++ /dev/null @@ -1,240 +0,0 @@ -#!/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 1 )) && (( bi+1 < oct_count )); then - oct[$((bi+1))]=$(( oct[$((bi+1))] | (v >> (8 - bo)) )) - fi - done - - TEXT_PACKED="" - for (( i=0; i/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" diff --git a/shell/send_sms_ucs2.sh b/shell/send_sms_ucs2.sh deleted file mode 100755 index a257805..0000000 --- a/shell/send_sms_ucs2.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -PORT="/dev/ttyUSB2" -NUMBER="+7..." -TEXT="Привет 412385" -SMSC="+79585370690" - -# Функция: номер → swapped nibbles -format_number() { - local num="${1#+}" - local len=${#num} - if (( len % 2 != 0 )); then - num="${num}F" - fi - local formatted="" - for (( i=0; i<${#num}; i+=2 )); do - formatted="${formatted}${num:i+1:1}${num:i:1}" - done - echo "$formatted" -} - -# --- SMSC --- -SMSC_FMT=$(format_number "$SMSC") -SMSC_DIGITS="${SMSC#+}" -SMSC_LEN=$(printf "%02X" $(( (${#SMSC_DIGITS} + 1) / 2 + 1 ))) -SMSC_PART="${SMSC_LEN}91${SMSC_FMT}" - -# --- DA (Destination Address) --- -DEST_DIGITS="${NUMBER#+}" # ← убираем плюс -DEST_FMT=$(format_number "$NUMBER") -DEST_LEN=$(printf "%02X" ${#DEST_DIGITS}) # ← длина в цифрах, НЕ с плюсом! -DEST_PART="${DEST_LEN}91${DEST_FMT}" - -# --- Текст (UCS2 / UTF-16BE) --- -TEXT_HEX=$(echo -n "$TEXT" | iconv -f UTF-8 -t UTF-16BE | xxd -p | tr 'a-f' 'A-F' | tr -d '\n') -TEXT_LEN_BYTES=$(( ${#TEXT_HEX} / 2 )) -TEXT_LEN_HEX=$(printf "%02X" $TEXT_LEN_BYTES) - -# --- TPDU --- -# FO=01, MR=00, DA, PID=00, DCS=08(UCS2), UDL, текст -TPDU="0100${DEST_PART}0008${TEXT_LEN_HEX}${TEXT_HEX}" -TPDU_LEN_DEC=$(( ${#TPDU} / 2 )) -FULL_PDU="${SMSC_PART}${TPDU}" - -echo "=========================================" -echo "Текст: $TEXT" -echo "HEX текста: $TEXT_HEX" -echo "DA Length: $DEST_LEN (${#DEST_DIGITS} цифр)" -echo "Длина TPDU: $TPDU_LEN_DEC байт" -echo "Итоговая PDU:" -echo "$FULL_PDU" -echo "=========================================" - -# --- Настройка модема --- -printf 'AT+CMEE=2\r' > "$PORT"; sleep 0.3 -printf 'AT+CMGF=0\r' > "$PORT"; sleep 0.3 - -# --- Чтение ответов в фоне --- -cat "$PORT" & -READER_PID=$! -sleep 0.3 - -# --- Отправка --- -printf 'AT+CMGS=%d\r' "$TPDU_LEN_DEC" > "$PORT" -sleep 1 -printf '%s\x1A' "$FULL_PDU" > "$PORT" -echo 'Отправлен sms ?' - -# Ждём ответ (до 30 сек, т.к. сеть может думать долго) -sleep 30 -kill $READER_PID 2>/dev/null