logic and flags update
This commit is contained in:
359
shell/send_sms.sh
Executable file
359
shell/send_sms.sh
Executable file
@ -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<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_HEX=""
|
||||||
|
for (( i=0; i<oct_count; i++ )); do
|
||||||
|
TEXT_HEX+="$(printf '%02X' "${oct[$i]}")"
|
||||||
|
done
|
||||||
|
UDL=$n
|
||||||
|
}
|
||||||
|
|
||||||
|
build_ucs2() {
|
||||||
|
local raw
|
||||||
|
raw=$(printf '%s' "$1" | iconv -f UTF-8 -t UTF-16BE 2>/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
|
||||||
@ -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<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"
|
|
||||||
@ -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
|
|
||||||
Reference in New Issue
Block a user