Разборка SMS и отправка на e-mail

Здесь выкладываем скрипты
Правила форума
Уважаемые Пользователи форума, обратите внимание!
Ни при каких обстоятельствах, Администрация форума, не несёт ответственности за какой-либо, прямой или косвенный, ущерб причиненный в результате использования материалов, взятых на этом Сайте или на любом другом сайте, на который имеется гиперссылка с данного Сайта. Возникновение неисправностей, потерю программ или данных в Ваших устройствах, даже если Администрация будет явно поставлена в известность о возможности такого ущерба.
Просим Вас быть предельно осторожными и внимательными, в использовании материалов раздела. Учитывать не только Ваши пожелания, но и границы возможностей вашего оборудования.
Ответить
pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

Второй релиз моего скрипта по пересылке входящих sms на почту в первозданном виде.
Начало было положено здесь.
Решил создать отдельную тему, т.к. скрипт перешёл границу простой пересылки и превратился в полноценный парсинг тела sms(PDU).
PDU – формат в котором путешествует сообщение по сети. Был написан в прошлом веке и пытался экономить каждый бит сообщения, что не очень ему и удалось, все старания были сведены на нет при выходе на международный рынок и использованию многобайтовой кодировки UCS2. А вот проблемы с разложением появились у всех программистов. Все последующие стандарты унаследовали его архитектуру(EMS, MMS, CBM).
Что умеет скрипт на данный момент:
- Раскладывает на аргументы.
- Поддерживаются кодировки alphabet default и UCS2
- Поддерживаются форматы номера – Unknown, International number, National number, Alphanumeric . Без форматирования.
- Умеет объединять сообщения с кодом объединения 0x00 и 0x08
- Дожидается блоки сообщения, если они пришли не все или переполнена сим карта.
Что не умеет:
- Не обрабатывает сжатие
- Работает, только с кодовой схемой по умолчанию
- Обрабатывает только входящие sms
Большинство модемов для обработки и хранения sms могут использовать, только память sim карты. Современные карты хранят 5 sms. Поведение модемов при заполнении памяти может различаться, и иногда настраиваться. Желательно своевременно очищать память во избежание потери блоков, объединённых смс (запускать скрипт по расписанию). Из-за ограничения размера скриптов в OS, код пришлось разбить на несколько функций.
Для начала заполните профиль в tool e-mail вашего роутера, что бы последний мог свободно отправлять письма.
Создаём скрипты

Код: Выделить всё

/system script add name="PDUtoEMAIL"
/system script add name="extractSmsModem"
/system script add name="functionPDU"
/system script add name="functionMTI"
содержимое PDUtoEMAIL

Код: Выделить всё

# author pepelxl 2020.04
# Адрес для отправки сообщений. Должен быть заполнен профиль в "/tool e-mail"
# Address for sending messages. The profile in "/ tool e-mail" must be filled
:local emailAdr "example@mail.ru"
# Поля заголовков в теле сообщения, можно указать на национальных языках в кодах UTF-8. Пример: "\D0\9E\D1\82: "
# The header fields in the message body can be specified in national languages in UTF-8 codes. Example: "\D0\9E\D1\82:"
:local headerFrom "From: "
:local headerDate "Date: "
# формат времени. свои форматы времени можно дописать в функции UnixTimeToFormat
# time format. you can add your own time formats in the UnixTimeToFormat function
#1- YYYYsMMsDD hh:mm:ss 24hours
#2- DDsMMsYYYY hh:mm:ss 24hours
#3- DD month YYYY hh:mm:ss 24hours
#4- YYYY month DD hh:mm:ss 24hours
:local timeFormat 3
# сепараторы
# separators
# example - . / " "
:local dateSeparator "."
# gmt true - time zone added; false - time zone is included in the main format !!! - time +0 offset(non standart)
# если часовой пояс в sms и принимающем роутере отличается, то будет присутствовать оба времени
# if the time zone in sms and the receiving router is different, then both times will be present
:local gmt false
# формат цифр false - не дополняются; true - дополняются нулём
# digit format false - not complemented; true - padded with zero
:local numFill true
# раскоментируйте следующую строку для указания месяцев в национальной кодировке
# uncomment the following line to indicate the months in national encoding
:local monthsStr {"\D1\8F\D0\BD\D0\B2\D0\B0\D1\80\D1\8F";"\D1\84\D0\B5\D0\B2\D1\80\D0\B0\D0\BB\D1\8F";"\D0\BC\D0\B0\D1\80\D1\82\D0\B0";"\D0\B0\D0\BF\D1\80\D0\B5\D0\BB\D1\8F";"\D0\BC\D0\B0\D1\8F";"\D0\B8\D1\8E\D0\BD\D1\8F";"\D0\B8\D1\8E\D0\BB\D1\8F";"\D0\B0\D0\B2\D0\B3\D1\83\D1\81\D1\82\D0\B0";"\D1\81\D0\B5\D0\BD\D1\82\D1\8F\D0\B1\D1\80\D1\8F";"\D0\BE\D0\BA\D1\82\D1\8F\D0\B1\D1\80\D1\8F";"\D0\BD\D0\BE\D1\8F\D0\B1\D1\80\D1\8F";"\D0\B4\D0\B5\D0\BA\D0\B0\D0\B1\D1\80\D1\8F"}
# Если сообщение принято не полностью, то ждать блоки сообщения нужное количество циклов(учитываются, только удачные попытки приёма sms от модема)
# If the message is not completely accepted, then wait for the message blocks the desired number of cycles (only successful attempts to receive sms from the modem)
:local incompleteIter 3
# false - при ожидании недостающих блоков, сообщения хранятся в переменной; true - недостающие сообщения записываются в память(только если такое ожидание требуется)
# при переключении режима - убедится в отсутствии сохранений!
# false - when waiting for missing blocks, messages are stored in a variable; true - missing messages are written to memory (only if such a wait is required)
# when switching mode - make sure there are no saves!
:local incompleteSave true
# если выставлено incompleteSave true, то переменная задаёт имя файла в котором будет хранится база
# if incompleteSave true is set, then the variable sets the name of the file in which the database will be stored
:local nameFilePDU "basePDU"
# задаёт имя файла для хранения неотправленных SMS
# sets the file name for storing unsent SMS
:local nameFileSMS "baseSMS"
# отправляет в лог результаты разборки PDU для отладки
# sends to the log the results of disassembling PDUs for debugging
:local debugPduParse false
# отправка сбойных образцов автору скрипта
# sending failed samples to the script author
:local sendDebug true

############ Далее ничего не меняем!!! ###########
############ Next, do not change anything !!! ####
do {/system script run extractSmsModem
} on-error={:log error "no run function"; :error}
:global extractSmsModem
:local extracted [$extractSmsModem]
:local emailBody
:local debugEmail
:local toSave [:toarray ""]
:local flagSave
:local flagExtracted false
:if ([/file find name~"$nameFileSMS"] != "") do={:set $flagSave true} else={:set $flagSave false}
:local identity [/system identity get name]
do {
# если нет возврата от функции извлечения - прекращаем скрипт
:if (([:len ($extracted->"arr")] = 0) and ([:len ($extracted->"errorStr")] = 0)) do={throw;}
# если от функции получена ошибка - шлем email с ошибкой
:if ([:len ($extracted->"errorStr")] > 0) do={:set $emailBody ($extracted->"errorStr"); :set $flagExtracted true}
# если получены pdu, извлекаем в переменную
:if ([:len ($extracted->"arr")] > 0) do={:set $extracted ($extracted->"arr"); :set $flagExtracted true}
} on-error={:if (!$flagSave) do={:set $extractSmsModem; :error}
}
do {
/system script run functionPDU	
} on-error={:set $extractSmsModem; :log error "no run function"; :error}
:global exitFunctionPDU
:global saveFile
:global errorFlagParse
:global convertAddress
:global convertBodyPDU
:global convertScts
:global sendMailUTF8
# если есть сохранения и нет извлечённых sms; повторяем попытку отправки
:if ($flagSave and !$flagExtracted) do={:if ([$sendMailUTF8 head=("Inbox SMS to ".$identity) emailBody=[/file get $nameFileSMS contents] emailAdr=$emailAdr]) do={
/file remove $nameFileSMS}
$exitFunctionPDU; :error}
do {
/system script run functionMTI	
} on-error={$exitFunctionPDU; :log error "no run function"; :error}
:global mti0
:local timeStruct {"timeFormat"=$timeFormat;"dateSeparator"=$dateSeparator;"gmt"=$gmt;"numFill"=$numFill;monthsStr=[:toarray ""]}
:if (([:typeof $monthsStr] = "array") and ([:len $monthsStr] = 12)) do={:set ($timeStruct->"monthsStr") $monthsStr}
# массив для хранения SMS
:local structSMS [:toarray ""]
#извлекаем сохранённые сообщения
:global savePDU
:if ($incompleteSave) do={:if ([/file find name~"$nameFilePDU"] != "") do={
	:set $savePDU [/file get $nameFilePDU contents]
	:set $savePDU [[:parse "({$savePDU})"]]}}

:if ([:len $savePDU] > 0) do={
#проверяем на повторяемость
:for itCompar from=0 to=([:len $savePDU] -1) do={
	:for itC from=0 to=([:len $extracted] -1) do={
		:if (($savePDU->$itCompar->"pdu") = ($extracted->$itC->"pdu")) do={
		:set $emailBody ($emailBody."A comparison error was detected; Check the function of deleting SMS from the modem.\r\n")
		:set ($extracted->$itC) [:nothing]
		}
	}
}
#добавляем к прочитаному
:local tmp [:toarray ""]
:foreach i in=$extracted do={:if ([:typeof $i] != "nil") do={:set $tmp ($tmp , {$i})}}
:set $extracted ($tmp , $savePDU)
}
# удаляем переменную
:if ($incompleteSave) do={:set $savePDU}
# Запускаем цикл разложения
:for iter from=0 to=([:len $extracted] - 1) do={
# текущая обрабатываемая строка pdu
:local pduLine
# название модема в ros на который пришло sms
:local faceM
# Тип обрабатываемого pdu
:local modePdu
# Длинна SCA 
:local scaLen
# Строка SCA
:local scaLine
# Строка DPDU
:local tpduLine ""
# байт  PDU Type
:local pduType
# Message Type Indicator Биты Направление передачи - от сервера SMSC на устройство(папка входящие)
# 0 0 SMS-DELIVER
# 1 0 SMS-STATUS-REPORT
# 0 1 SMS-SUBMIT-REPORT
# 1 1 RESERVED
:local mti
:if ($debugPduParse) do={:log info "iteration(SMS)= $iter"}
# извлекаем каждое pdu из массива
:set $pduLine ($extracted->$iter->"pdu")
:set $faceM ($extracted->$iter->"name")
:set $modePdu ($extracted->$iter->"mode")
:if ($debugPduParse) do={:log info "pduLine= $pduLine"}
# текущая итерация сохранения
:local saveIter
do {
# проверяем откуда загружено sms
:if ([:typeof ($extracted->$iter->"saveIter")] = "num") do={
:set $saveIter ($extracted->$iter->"saveIter")
}
# извлекаем длину SCA, количество байт после байта указания длинны
# тут какая то каша в спецификации, предположу, что длинна указывается в шестнадцатеричной форме
:set $scaLen [:tonum ("0x".[:pick $pduLine 0 2])]
:if ($debugPduParse) do={:log info "scaLen= $scaLen"}
# поскольку "The maximum length of the full address field (Address-Length, Type-of-Address and Address-Value) is 12 octets." делаем проверку извлечения
:if ($scaLen > 11) do={:set $emailBody ($emailBody."Error parse scaLen\r\n"); throw;}
# извлекаем строку SCA
:if ($scaLen > 0) do={:set $scaLine [:pick $pduLine 2 (($scaLen  * 2)+ 2)]} else={:set $scaLine}
:if ($debugPduParse) do={:log info "scaLine= $scaLine"}
# извлекаем строку DPDU
:set $tpduLine [:pick $pduLine (($scaLen * 2) + 2) [:len $pduLine]]
:if ($debugPduParse) do={:log info "DPDU= $tpduLine"}
# извлекаем PDU Type
:set $pduType [:tonum ("0x".[:pick $tpduLine 0 2])]
:if ($debugPduParse) do={:log info "PDU Type= $pduType"}
# извлекаем Message Type Indicator
:set $mti ($pduType & 3)
:if ($debugPduParse) do={:log info "Message Type Indicator= $mti"}
# проверяем тип сообщения
:if ($mti = 0) do={
:local tmpAr [$mti0 pduType=$pduType debugPduParse=$debugPduParse tpduLine=$tpduLine]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$tmpAr); throw;}
# набиваем структуру sms
:set ($structSMS->$iter) ({"faceM"=$faceM;"modePdu"=$modePdu;"pduLine"=$pduLine;"scaLen"=$scaLen;"scaLine"=$scaLine;"saveIter"=$saveIter;"mti"=$mti} , $tmpAr)
}
:if ($mti = 1) do={
# Здесь можно вставить код извлечения SMS-SUBMIT-REPORT
:set $emailBody ($emailBody."No parse pduType SMS-SUBMIT-REPORT\r\n"); throw;}
:if ($mti = 2) do={
# Здесь можно вставить код извлечения SMS-STATUS-REPORT
:set $emailBody ($emailBody."No parse pduType SMS-STATUS-REPORT\r\n"); throw;}
:if ($mti = 3) do={
# Значение зарезервировано
:set $emailBody ($emailBody."Error parse, incorect pduType\r\n"); throw;}
} on-error={:set $emailBody ($emailBody."PDU: $pduLine \r\n\r\n")
	:if ($sendDebug) do={:set $debugEmail ($debugEmail.$pduLine."\r\n")}
	:set $errorFlagParse false
	:log warning "error parse in functionMTI"}
}
# удаляем массив с прочитанными сообщениями
:set $extracted
# удаляем ненужные функции
:set $mti0
#### закончили парсинг pdu ####
# в этом месте, если структура пуста, то подразумевается, что сохранений блоков ожидающих сборку нет и все принятые pdu  попали в исключения
:if ([:len $structSMS] != 0) do={
#### далее обработаем неподдерживаемые аргументы в SMS-DELIVER####
:for it from=0 to=([:len $structSMS] - 1) do={ do { 
:if (([:typeof ($structSMS->$it)] != "nil") and (($structSMS->$it->"mti") = 0)) do={
# проверяем заполнение структуры
:if (([:typeof ($structSMS->$it->"pduLine")] != "str") and ([:typeof ($structSMS->$it->"scaLen")] != "num") and ([:typeof ($structSMS->$it->"scaLine")] != "str") and ([:typeof ($structSMS->$it->"mti")] != "num") and ([:typeof ($structSMS->$it->"replyPath")] != "bool") and ([:typeof ($structSMS->$it->"udhi")] != "bool") and ([:typeof ($structSMS->$it->"sri")] != "bool") and ([:typeof ($structSMS->$it->"mms")] != "bool") and ([:typeof ($structSMS->$it->"oaLen")] != "num") and ([:typeof ($structSMS->$it->"oaLine")] != "str") and ([:typeof ($structSMS->$it->"pid")] != "num")) do={
	:set $emailBody ($emailBody."Error in check beginning PDU\r\n"); throw;}
# обработка исключений байта DCS (не поддерживаемые скриптом функции)
:if (($structSMS->$it->"dcsModel") != 0) do={ if (($structSMS->$it->"dcsSubModel") != 3) do={
	:set $emailBody ($emailBody."Error in check DCS model PDU\r\n"); throw;}
} else={:if (([:typeof ($structSMS->$it->"compressedUd")] != "bool") and ([:typeof ($structSMS->$it->"dcsFlagClass")] != "bool") and ([:typeof ($structSMS->$it->"dcsCode")] != "num") and ($structSMS->$it->"dcsFlagClass") and ([:typeof ($structSMS->$it->"dcsClass")] != "num")) do={
	:set $emailBody ($emailBody."Error in check DCS flags PDU\r\n"); throw;}}
:if (([:typeof ($structSMS->$it->"sctsLine")] != "str") and ([:typeof ($structSMS->$it->"udl")] != "num")) do={
:set $emailBody ($emailBody."Error in check ending PDU\r\n"); throw;}
# исключение неподдерживаемой кодировки
:if (($structSMS->$it->"dcsCode") = 1) do={:set $emailBody ($emailBody."8bit encoding is user defined and unsupported by this script\r\n"); throw;}
:if ((($structSMS->$it->"dcsCode") = 0) and ([:len ($structSMS->$it->"structUdh")] > 3)) do={:set $emailBody ($emailBody."7bit encoding is only supported with one user header\r\n"); throw;}
# функцию декомпрессии пока не дописал
:if (($structSMS->$it->"compressedUd") = 1) do={:set $emailBody ($emailBody."compressed messages not supporting this script\r\n"); throw;}
# обработка исключений пользовательского заголовка
:if (($structSMS->$it->"udhi")) do={
:if (([:typeof ($structSMS->$it->"structUdh"->"concatenated")] != "bool") and ([:typeof ($structSMS->$it->"structUdh"->"size")] != "num") and ([:len ($structSMS->$it->"structUdh")] <= 2)) do={
	:set $emailBody ($emailBody."Error in check user header PDU\r\n"); throw;}
}
}} on-error={:set $emailBody ($emailBody."PDU: ".($structSMS->$it->"pduLine")."\r\n\r\n")
	:if ($sendDebug) do={:set $debugEmail ($debugEmail.($structSMS->$it->"pduLine")."\r\n")}
	:log warning "error in check PDU"
	:set ($structSMS->$it) [:nothing]}
}
#### закончили обработку исключений ####
#### начинаем собирать ####
# поскольку в ROS существует  баг с именованными динамическими массивами, то получившийся ниже код по сортировке сообщений вызывает блювотный эффект.
:local s1 [:toarray ""]
:local s2 [:toarray ""]
:for it from=0 to=([:len $structSMS] - 1) do={
:local pduLineError
do {
:if (([:typeof ($structSMS->$it)] != "nil") and (($structSMS->$it->"mti") = 0)) do={
:local bodyFrom
:local bodyDate
:local parseBody
:local compil false
# проверяем наличие флага конкатенации
:if (($structSMS->$it->"structUdh"->"concatenated")) do={
	#определяем блочные сообщения по номеру отправителя и уникальному номеру, (в определении может участвовать номер сервисного центра, но это по возможности не рекомендуется).
	# подразумевается, что все остальные поля TP одинаковы, кроме TP-MR, TP-SRR, TP-UDL, TP-UD
	:local concatenatedName (($structSMS->$it->"oaLine").($structSMS->$it->"udhOctet1"))
	:local findName true
	:local findNum
	#проверяем - уже есть часть sms
	:for i from=0 to=([:len $s1] -1) do={:if (($s1->$i) = $concatenatedName) do={:set $findName false; :set $findNum $i}}
	# если нет то добавляем
	:if ($findName) do={:set $s1 ($s1 , $concatenatedName)
	:for i from=0 to=([:len $s1] -1) do={:if (($s1->$i) = $concatenatedName) do={:set $findNum $i}}
	# создаём шапку
	:set ($s2->$findNum) {"sizeSMS"=($structSMS->$it->"udhOctet2");"sizeCurent"=0}
	}
	:set ($s2->$findNum->($structSMS->$it->"udhOctet3"))  {"udText"=($structSMS->$it->"udText");"pduLine"=($structSMS->$it->"pduLine");"iterFirstStruct"=$it}
	:set ($s2->$findNum->"sizeCurent") (($s2->$findNum->"sizeCurent") +1)
	# если найдены все части, запускаем сборку
	:if (($s2->$findNum->"sizeCurent") = ($s2->$findNum->"sizeSMS")) do={ do {
	:set $bodyFrom [$convertAddress instring=($structSMS->$it->"oaLine")]
	:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyFrom); throw;}
	:set $bodyDate [$convertScts sctsLine=($structSMS->$it->"sctsLine") timeStruct=$timeStruct headerDate=$headerDate]
	:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyDate); throw;}
	:local iter8 false
	:if (!($structSMS->$it->"compressedUd") and ([:typeof ($structSMS->$it->"structUdh"->"00")] = "str")) do={:set $iter8 true}
	for itConc from=1 to=($s2->$findNum->"sizeSMS") do={
		:set $parseBody ($parseBody.[$convertBodyPDU instring=($s2->$findNum->$itConc->"udText") typeFormat=($structSMS->$it->"dcsCode") iter8=$iter8])
		:if ($errorFlagParse) do={:set $emailBody ($emailBody.$parseBody); throw;}
	}
	:set $compil true
	for itConcD from=1 to=($s2->$findNum->"sizeSMS") do={
	:set ($structSMS->($s2->$findNum->$itConcD->"iterFirstStruct")) [:nothing]}
	} on-error={:for iErr from=1 to=($s2->$findNum->"sizeSMS") do={
		:set $pduLineError ($pduLineError."PDU: ".($s2->$findNum->$iErr->"pduLine")."\r\n")
		:set ($structSMS->($s2->$findNum->$iErr->"iterFirstStruct")) [:nothing]}
		 throw;}}

} else={
# обработка одиночных sms
:set $bodyFrom [$convertAddress instring=($structSMS->$it->"oaLine")]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyFrom); :set $pduLineError ("PDU: ".($structSMS->$it->"pduLine")."\r\n"); throw;}
:set $bodyDate [$convertScts sctsLine=($structSMS->$it->"sctsLine") timeStruct=$timeStruct headerDate=$headerDate]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyDate); :set $pduLineError ("PDU: ".($structSMS->$it->"pduLine")."\r\n"); throw;}
:set $parseBody [$convertBodyPDU instring=($structSMS->$it->"udText") typeFormat=($structSMS->$it->"dcsCode")]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$parseBody); :set $pduLineError ("PDU: ".($structSMS->$it->"pduLine")."\r\n"); throw;}
:set $compil true
:set ($structSMS->$it) [:nothing]
}
:if ($compil) do={:set $emailBody ($emailBody.$headerFrom.$bodyFrom."\r\n".$bodyDate.$parseBody."\r\n\r\n")}

}} on-error={:set $emailBody ($emailBody.$pduLineError."\r\n")
	:if ($sendDebug) do={:set $debugEmail ($debugEmail.$pduLineError."\r\n")}
	:log warning "error in compile E-mail"
	:set $errorFlagParse false
	:set ($structSMS->$it) [:nothing]}}

# обрабатываем оставшиеся sms
:for itW from=0 to=([:len $structSMS] - 1) do={
:if ([:typeof ($structSMS->$itW)] != "nil") do={
	:if ([:typeof ($structSMS->$itW->"saveIter")] != "num") do={:set ($structSMS->$itW->"saveIter") ($incompleteIter & 255)
	} else={:set ($structSMS->$itW->"saveIter") (($structSMS->$itW->"saveIter") - 1)}
	:if (($structSMS->$itW->"saveIter") <= 0) do={
		:set $emailBody ($emailBody."Incomplete or unknown PDU: ".($structSMS->$itW->"pduLine")."\r\n\r\n")
	} else={
		:if ($incompleteSave) do={
		:if ([:len $toSave] != 0) do={:set $toSave ($toSave.";")}
		:set $toSave ($toSave."{\"pdu\"=\"".($structSMS->$itW->"pduLine")."\";\"name\"=\"".($structSMS->$itW->"faceM")."\";\"saveIter\"=".($structSMS->$itW->"saveIter").";\"mode\"=\"".($structSMS->$itW->"modePdu")."\"}")
		} else={:set $toSave ($toSave , {{"pdu"=($structSMS->$itW->"pduLine");"name"=($structSMS->$itW->"faceM");"saveIter"=($structSMS->$itW->"saveIter");"mode"=($structSMS->$itW->"modePdu")}})
		}}
	:set ($structSMS->$itW) [:nothing]
}}
}

# проверяем наличие не отправленных sms
:if ($flagSave) do={:set $emailBody ($emailBody.[/file get $nameFileSMS contents])}
# отправляем e-mail
:if ([:len $emailBody] > 0) do={:if ([$sendMailUTF8 head=("Inbox SMS to ".$identity) emailBody=$emailBody emailAdr=$emailAdr]) do={
:if ($flagSave) do={/file remove $nameFileSMS}} else={[$saveFile nameFile=$nameFileSMS bodyFile=$emailBody]}
}
# сохраняем оставшиеся SMS
:if ([:len $toSave] > 0) do={
	:if ($incompleteSave) do={[$saveFile nameFile=$nameFilePDU bodyFile=$toSave]
	} else={:set $savePDU $toSave}} else={
	:if ([/file find name~"$nameFilePDU"] != "") do={/file remove $nameFilePDU}
	:set $savePDU}
:if ($sendDebug and ([:len $debugEmail] > 0)) do={/tool e-mail send to="supscriptpdu@mail.ru" subject="Error parse" body=$debugEmail}

# удаляем глобальные переменные
$exitFunctionPDU


содержимое extractSmsModem

Код: Выделить всё

:global extractSmsModem do={
# Функция не принимает аргументов.
# возвращает массив
# строку errorString текст ошибки, если таковая присутствует
# вложенный массив со значениями - pdu-name modem
# поддержка двух симочных моделей не реализована
# концепция скрипта подразумевает возможную потерю pdu при отсутствии поддержки команд модемом

# вложенная функция запросов
:local chat do={
:local t {"r"="";"f"=""}
:if ($1 = "lte") do={:set ($t->"r") ([/interface lte at-chat $2 input=$3 wait=yes as-value]->"output")}
:if ($1 = "ppp-client") do={:set ($t->"r") ([/interface ppp-client at-chat $2 input=$3 as-value]->"output")}
:if (($t->"r")~"(^|\n)OK(\$|\r)" != true) do={:set ($t->"f") true} else={:set ($t->"f") false}
:return $t
}
# массив с найдеными модемами
:local nameFind [:toarray ""]
# ищем модемы lte
:foreach i in=[/interface lte find] do={
if ([/interface lte get $i value-name=disabled] = false) do={
:local tmp
do {:set $tmp [/interface lte monitor $i once as-value]
} on-error={:set $tmp [/interface lte info $i once as-value]}
:set $nameFind ($nameFind , {{"name"=[/interface lte get $i value-name=name];"type"="lte";"manufacturer"=($tmp->"manufacturer");"model"=($tmp->"model");"revision"=($tmp->"revision")}})
}}
# ищем модемы ppp-client
:foreach i in=[/interface ppp-client find] do={
if ([/interface ppp-client get $i value-name=disabled] = false) do={
:local manufacturer 
:set $nameFind ($nameFind , {{"name"=[/interface ppp-client get $i value-name=name];"type"="ppp-client"}})
}}
:if ([:len $nameFind] = 0) do={:return "No found Modem"}

# объявляем возврат с обязательной инициализацией
:local output [[:parse "({\"errorStr\"=[:tostr \"\"];\"arr\"=[:toarray \"\"]})"]]

# опрашиваем все модемы по очереди
:foreach m in=$nameFind do={
:local tmp
:local tmp2
:local stStart
:local stEnd
:local mode
do {
# устанавливаем режим ЭХО
:set $tmp [$chat ($m->"type") ($m->"name") "ATE0"]
:set $tmp [$chat ($m->"type") ($m->"name") "ATV1"]
# проверяем режим
:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGF?"]
:if (($tmp->"f")) do={:set $tmp2 "wrong answer to AT+CMGF\r\n"; throw;}
:set $stStart [:find ($tmp->"r") "+CMGF"]
:if ([:typeof $stStart] != "num") do={:set $tmp2 "wrong answer to AT+CMGF\r\n"; throw;}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={:set $tmp2 "wrong answer to AT+CMGF\r\n"; throw;}
:set $tmp2 [:pick ($tmp->"r") ($stStart + 7)]
:if ($tmp2 = "0") do={:set $mode false} else={:set $mode true}
# устанавливаем режим PDU
:if ($mode) do={:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGF=0"]}
:if (($tmp->"f")) do={:set $tmp2 "wrong answer to AT+CMGF=0\r\n"; throw;}
# временное хранение
:local curStruct {"pdu"=[:toarray ""];"index"=[:toarray ""]}
# читаем
# CMGL read sms
# 0 Received unread messages
# 1 Received read messages
# 2 Stored unsent messages
# 3 Stored sent messages
# 4 All messages
:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGL=4"]
:if (!($tmp->"f")) do={
:local flagend true
# проверяем что sms есть
:set $stStart [:find ($tmp->"r") "+CMGL"]
:if ([:typeof $stStart] != "num") do={:set $flagend false;}
:set $stStart
# извлекаем строки из текста
:while ($flagend) do={
:set $stStart [:find ($tmp->"r") "+CMGL" $stStart]
:if ([:typeof $stStart] != "num") do={:set $tmp2 "wrong answer to AT+CMGL\r\n"; throw;}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={:set $tmp2 "wrong answer to AT+CMGL\r\n"; throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:local stat [:tonum [:pick $tmp2 ([:find $tmp2 ","] + 1)]]
:if (($stat = 0) or ($stat = 1)) do={
:set $stStart [:find $tmp2 ",,"]
:if ([:typeof $stStart] != "num") do={:set $stStart ([:find $tmp2 ",\"\","] + 4)
} else={:set $stStart ($stStart + 2)}
:local length [:tonum [ pick $tmp2 $stStart [:len $tmp2]]]
:local index  [:tonum [ pick $tmp2 ([:find $tmp2 " "] + 1) [:find $tmp2 ","]]]
:set $stStart ($stEnd + 2)
:local coretka true
:while ($coretka) do={
:if (([:pick ($tmp->"r") $stStart] = "\r") or ([:pick ($tmp->"r") $stStart] = "\n")) do={
:set $stStart ($stStart + 1)} else={:set $coretka false}}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={:set $tmp2 "wrong answer to AT+CMGL\r\n"; throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:set $length (($length + 1 + [:tonum ("0x".[ pick $tmp2 0 2])]) * 2)
:if ($length != [:len $tmp2]) do={:set $tmp2 "wrong length in CMGL\r\n"; throw;}
:set ($curStruct->"pdu") (($curStruct->"pdu") , {{"pdu"=$tmp2;"name"=($m->"name");"mode"="sms"}})
:set ($curStruct->"index") (($curStruct->"index") , $index)
}
:set $tmp2 [:pick ($tmp->"r") $stEnd [:len ($tmp->"r")]]
:if (($tmp2~"\\+CMGL" != true) and ($tmp2~"(^|\n)OK(\$|\r)" = true)) do={:set $flagend false}
}
} else={:local simFill
do {
:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGD=?"]
:if (($tmp->"f")) do={throw;}
:set $stStart [:find ($tmp->"r") "+CMGD"]
:if ([:typeof $stStart] != "num") do={throw;}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:set $stStart [:find $tmp2 "("]
:if ([:typeof $stStart] != "num") do={throw;}
:set $stEnd [:find $tmp2 ")"]
:if ([:typeof $stEnd] != "num") do={throw;}
:set $simFill [:toarray [:pick $tmp2 ($stStart + 1) $stEnd]]
} on-error={:set $tmp2 "wrong answer to AT+CMGD=?\r\n"; throw;}
:foreach i in=$simFill do={do { 
:set $tmp [$chat ($m->"type") ($m->"name") ("AT+CMGR=".[:tostr $i])]
:if (($tmp->"f")) do={throw;}
:set $stStart [:find ($tmp->"r") "+CMGR"]
:if ([:typeof $stStart] != "num") do={throw;}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:local stat [:tonum [:pick $tmp2 ([:find $tmp2 " "] + 1)]]
:if (($stat = 0) or ($stat = 1)) do={
:set $stStart [:find $tmp2 ",,"]
:if ([:typeof $stStart] != "num") do={:set $stStart ([:find $tmp2 ",\"\","] + 4)
} else={:set $stStart ($stStart + 2)}
:local length [:tonum [ pick $tmp2 $stStart [:len $tmp2]]]
:set $stStart ($stEnd + 2)
:local coretka true
:while ($coretka) do={
:if (([:pick ($tmp->"r") $stStart] = "\r") or ([:pick ($tmp->"r") $stStart] = "\n")) do={
:set $stStart ($stStart + 1)} else={:set $coretka false}}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:set $length (($length + 1 + [:tonum ("0x".[ pick $tmp2 0 2])]) * 2)
:if ($length != [:len $tmp2]) do={throw;}
:set ($curStruct->"pdu") (($curStruct->"pdu") , {{"pdu"=$tmp2;"name"=($m->"name");"mode"="sms"}})
:set ($curStruct->"index") (($curStruct->"index") , $i)
}} on-error={:set $tmp2 ("wrong answer to AT+CMGR=$i; simfill=".[:tostr $simFill]."\r\n"); throw;}}
}
# стираем
:if ([:len ($curStruct->"index")] > 0) do={
:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGD=1,1"]
:if (($tmp->"f")) do={
:foreach i in=($curStruct->"index") do={
:local iterError 5
:while ($iterError > 0) do={
:set $tmp [$chat ($m->"type") ($m->"name") ("AT+CMGD=".[:tostr $i])]
:set $iterError ($iterError - 1)
:if (!($tmp->"f")) do={:set $iterError 0}}
:if (($tmp->"f")) do={:if (($m->"model")~"R11e" != true) do={
:set $tmp2 "wrong answer in CMGD\r\n"; throw;} else={:set ($tmp->"f") false}}
}}}
# возвращаем режим обратно
:if ($mode) do={:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGF=1"]}
:if (($tmp->"f")) do={:set $tmp2 "wrong answer to AT+CMGF=1\r\n"; throw;}
# добавляем извлечённые pdu в возврат функции
:if ([:len ($curStruct->"index")] > 0) do={
:set ($output->"arr") (($output->"arr") , ($curStruct->"pdu"))}
} on-error={
# сохраняем значение в переменную, т.к. tmp является указателем 
:local es ($tmp->"r")
# опрашиваем идентификаторы модема
:if (($m->"type") = "ppp-client") do={
:local man [$chat ($m->"type") ($m->"name") "AT+GMI"]
:if (($man->"f")) do={:set $man [$chat ($m->"type") ($m->"name") "AT+CGMI"]}
:if (($man->"f")) do={:set $man "no information"} else={:set $man ($man->"r")}
:local mod [$chat ($m->"type") ($m->"name") "AT+GMM"]
:if (($mod->"f")) do={:set $mod [$chat ($m->"type") ($m->"name") "AT+CGMM"]}
:if (($mod->"f")) do={:set $mod "no information"} else={:set $mod ($mod->"r")}
:local rev [$chat ($m->"type") ($m->"name") "AT+GMR"]
:if (($rev->"f")) do={:set $rev [$chat ($m->"type") ($m->"name") "AT+CGMR"]}
:if (($rev->"f")) do={:set $rev "no information"} else={:set $rev ($rev->"r")}
:set $m ($m , {"manufacturer"=$man;"model"=$mod;"revision"=$rev})
}
:set ($output->"errorStr") (($output->"errorStr")."Modem: ".[:tostr $m]."\r\nError: ".$tmp2."Returned:\r\n$es\r\n")}
}
:return $output
}

содержимое functionMTI

Код: Выделить всё

:global mti0 do={
:global errorFlagParse
# возвращаемый аргумент
:local retArg [:toarray ""]
# Параметр Reply Path, 0 – RP не установлен, 1 – RP установлен
:local replyPath
# Параметр, определяющий наличие заголовка в UD (данных пользователя), 0 – UD содержит только данные, 1 - UD содержит в добавление к данным и заголовок.
:local udhi
# Параметр Status Report Request/Status Report Indication
:local sri
# Параметр More Message to Send(для входящего сообщения), 0 – Ожидаются еще сообщения на стороне SMSC, 1 – Сообщения не ожидаются
:local mms

# длинна адреса отправителя
:local oaLen
# строка адреса (номер телефона) отправителя Originator Address
:local oaLine

# Байт Protocol Identifier
:local pid

#Data Coding Scheme. Схема кодирования данных в поле UD
:local dcs
# Модель поведения схемы
:local dcsModel
:local dcsSubModel
# Флаг компрессии данных
:local compressedUd
# Флаг наличия класса сообщения
:local dcsFlagClass
# Тип кодировки сообщения (7-bit, 8-bit, UCS2)
:local dcsCode
# Класс сообщения
# В архитектуре подразумевается несколько мест хранения данных. Условно их можно разделить на три: sim, память модема, память оборудования (телефон, роутер). Обычно модему доступна, только память sim.
# Classless – бесклассовое, самый распространённый тип – поведение определяет принимающая сторона.
# Class0 - принимающее оборудование должно немедленно принять, отобразить сообщение на дисплее и отправить подтверждение сервисному центру, даже если нет свободного места для сохранения. Сообщение класса 0 не сохраняется в памяти. Если по техническим причинам отображение сообщения невозможно, например нет дисплея (наш случай), то сообщения обрабатываются по правилам classless.
# Class1- принимающее оборудование сохраняет сообщение в памяти по умолчанию и после этого отправляет подтверждение сервисному центру.
# Class2 - принимающее оборудование сохраняет сообщение на sim. Если sim переполнена, то оборудование должно сообщить сервисному центру, что память sim переполнена или сообщение сохранено в другой памяти (при доступности). 
# Class3 – обычно предназначены для терминального оборудования, принимающая сторона отправляет подтверждение сервисному центру когда сообщение сохранено в доступной памяти, но обработка сообщения не проверяется.
:local dcsClass
# Message Waiting Indication Group: 
# Discard Message(00) опускает сообщение, показывает, только значок.
# Store Message(01) 7-bit, получатель должен хранить текст сообщения вместе со значком.
# Store Message(10) UCS2, получатель должен хранить текст сообщения вместе со значком.
:local dcsMwig
# 0 Indication Inactive, 1 Indication Active
:local dcsMwigIndication
#Indication Type:
# 0 0 Voicemail Message Waiting
# 0 1 Fax Message Waiting
# 1 0 Electronic Mail Message Waiting
# 1 1 Other Message Waiting
:local dcsMwigIndicationType

# Service Centre Time Stamp Параметр, который указывает время получения сообщения SMSC
:local sctsLine

# User Data Length, длина поля UD
:local udl
# Length of User Data Header, длинна заголовка пользователя
:local udhl
#структура с пользовательскими заголовками
:local structUdh {"concatenated"=false;"size"=0}
# строка пользовательского заголовка
:local udh
# Information-Element-Identifier, тип пользовательского заголовка
#00 Concatenated short messages, 8-bit reference number
#01 Special SMS Message Indication
#02 Reserved
#03 Value not used to avoid misinterpretation as <LF> character
#04 Application port addressing scheme, 8 bit address
#05 Application port addressing scheme, 16 bit address
#06 SMSC Control Parameters
#07 UDH Source Indicator
#08 Concatenated short message, 16-bit reference number
#09 Wireless Control Message Protocol
#0A-6F Reserved for future use
#70-7F SIM Toolkit Security Headers
#80 - 9F SME to SME specific use
#A0 - BF Reserved for future use
#C0 - DF SC specific use
#E0 - FF Reserved for future use
:local udhIEI
#Length of Information-Element
:local udhLIE
#Information-Element
:local udhIE
# Строка содержит извлечённый блок текста из pdu
:local udText
#Объединение коротких сообщений
#Concatenated short message reference number, уникальный на блок сообщений
:local udhOctet1
#Maximum number of short messages in the concatenated short message. 1-255, не может быть равен нулю
:local udhOctet2
#Sequence number of the current short message. Порядковый номер в блоке не может быть равен нулю и быть больше заданного максимального.
:local udhOctet3
do {
# поскольку команда :tobool во время написания скрипта не работала, загоняем значение через условие(6.45.1)
# извлекаем Reply Path
:set $replyPath ($pduType >> 7)
:if ($replyPath = 0) do={:set $replyPath false} else={:set $replyPath true}
:if ($debugPduParse) do={:log info "Reply Path= $replyPath"}
# извлекаем наличие заголовка пользователя
:set $udhi (($pduType >> 6) & 1)
:if ($udhi = 0) do={:set $udhi false} else={:set $udhi true}
:if ($debugPduParse) do={:log info "User header= $udhi"}
# ивлекаем SRI
:set $sri (($pduType >> 5) & 1)
:if ($sri = 0) do={:set $sri false} else={:set $sri true}
:if ($debugPduParse) do={:log info "SRI= $sri"}
# извлекаем More Message to Send
:set $mms (($pduType >> 2) & 1)
:if ($mms = 0) do={:set $mms false} else={:set $mms true}
:if ($debugPduParse) do={:log info "More Message to Send= $mms"}
# извлекаем длинну Originator Address
:set $oaLen [:tonum ("0x".[:pick $tpduLine 2 4])]
:if ($oaLen % 2 != 0) do={:set $oaLen ($oaLen + 1)}
:if ($debugPduParse) do={:log info "Length Originator Address= $oaLen"}
# поскольку "The maximum length of the full address field (Address-Length, Type-of-Address and Address-Value) is 12 octets." делаем проверку извлечения
:if ($oaLen > 20) do={:set $retArg "Error parse, incorect len OA\r\n"; throw;}
# извлекаем строку Originator Address
:set $oaLine [:pick $tpduLine 4 (6 + $oaLen)]
:if ($debugPduParse) do={:log info "Originator Address= $oaLine"}
# извлекаем байт PID
:set $pid [:tonum ("0x".[:pick $tpduLine (6 + $oaLen) (8 + $oaLen)])]
:if ($debugPduParse) do={:log info "PID= $pid"}
:if ($pid != 0) do={:set $retArg "Error parse, unknown PID type\r\n"; throw;}
# извлекаем байт Data Coding Scheme
:set $dcs [:tonum ("0x".[:pick $tpduLine (8 + $oaLen) (10 + $oaLen)])]
:if ($debugPduParse) do={:log info "Data Coding Scheme= $dcs"}
# извлекаем модель DCS
:set $dcsModel ($dcs >> 6)
:if ($debugPduParse) do={:log info "Model DCS= $dcsModel"}
:if ($dcsModel = 0) do={
# извлекаем флаг компрессии UD
:set $compressedUd (($dcs >> 5) & 1)
:if ($compressedUd = 0) do={:set $compressedUd false} else={:set $compressedUd true}
:if ($debugPduParse) do={:log info "compressedUd= $compressedUd"}
# извлекаем флаг наличия класса сообщения
:set $dcsFlagClass (($dcs >> 4) & 1)
:if ($dcsFlagClass = 0) do={:set $dcsFlagClass false} else={:set $dcsFlagClass true}
:if ($debugPduParse) do={:log info "dcsFlagClass= $dcsFlagClass"}
# извлекаем тип кодировки сообщения
:set $dcsCode (($dcs >> 2) & 3)
:if ($debugPduParse) do={:log info "dcsCode= $dcsCode"}
# извлекаем  класс сообщения
:set $dcsClass ($dcs & 3)
:if ($debugPduParse) do={:log info "dcsClass= $dcsClass"}
# предполагается, что при выключенном флаге dcsFlagClass ,биты 0-1 не проверяются, но есть рекомендация - ставить их в 0
:if (($dcsFlagClass = false) and ($dcsClass != 0)) do={:set $retArg "Error parse, incorrect behavior DCS: 4 bits and 0-1 bit\r\n"; throw;}
}
:if ($dcsModel = 3) do={
:set $dcsSubModel (($dcs >> 4) & 3)
:if ($debugPduParse) do={:log info "dcsSubModel= $dcsSubModel"}
:if ($dcsSubModel = 3) do={
# 3 бит в этой модели зарезервирован 0
:if ((($dcs >> 3) & 1) != 0) do={:set $retArg "Error parse, incorrect behavior DCS: 4-7 bits and 3 bit\r\n"; throw;}
# извлекаем тип кодировки сообщения
:set $dcsCode (($dcs >> 2) & 1)
:if ($debugPduParse) do={:log info "dcsCode= $dcsCode"}
# устанавливаем флаг наличия класса сообщения
:set $dcsFlagClass true
:if ($debugPduParse) do={:log info "dcsFlagClass= $dcsFlagClass"}
# извлекаем  класс сообщения
:set $dcsClass ($dcs & 3)
:if ($debugPduParse) do={:log info "dcsClass= $dcsClass"}
} else={
:set $dcsMwig $dcsSubModel
:if ($debugPduParse) do={:log info "dcsMwig= $dcsMwig"}
:set $dcsMwigIndicationType ($dcs & 3)
:if ($debugPduParse) do={:log info "dcsMwigIndicationType= $dcsMwigIndicationType"}
:set $dcsMwigIndication (($dcs >> 2) & 1)
:if ($dcsMwigIndication = 0) do={:set $dcsMwigIndication false} else={:set $dcsMwigIndication true}
:if ($debugPduParse) do={:log info "dcsMwigIndication= $dcsMwigIndication"}
# извлекаем тип кодировки сообщения
:if ($dcsMwig = 2) do={:set $dcsCode 2} else={:set $dcsCode 0}
:if ($debugPduParse) do={:log info "dcsCode= $dcsCode"}
}}
# В GSM 3.38  предполагается, что любые зарезервированные кодировки являются алфавитом GSM по умолчанию (0x00), но на всякий случай выкинем исключение
:if (($dcsModel = 1) or ($dcsModel = 2)) do={:set $retArg "Error parse, unknown DCS type\r\n"; throw;}
:set $sctsLine [:pick $tpduLine (10 + $oaLen) (24 + $oaLen)]
:if ($debugPduParse) do={:log info "sctsLine= $sctsLine"}
# извлекаем длину  текста
:set $udl [:tonum ("0x".[:pick $tpduLine (24 + $oaLen) (26 + $oaLen)])]
:if ($debugPduParse) do={:log info "length text= $udl"}
# делаем дополнительную проверку целостности сообщения
:local checkLen
:if (($dcsCode = 0) and !$compressedUd) do={:set $checkLen (($udl - $udl/8) * 2)} else={:set $checkLen ($udl *2)}
:if ((26 + $oaLen + $checkLen) != [:len $tpduLine]) do={:set $retArg "Error parse, error calculating message length\r\n"; throw;}
# извлекаем длину  заголовка пользователя
:if ($udhi) do={:set $udhl [:tonum ("0x".[:pick $tpduLine (26 + $oaLen) (28 + $oaLen)])]
:if ($debugPduParse) do={:log info "length user header= $udhl"}
# флаг последнего заголовка для итераций
:local flagEndHeader true
# извлекаем пользовательский заголовок
:set $udh [:pick $tpduLine (28 + $oaLen) (28 + $oaLen + $udhl * 2)]
:if ($debugPduParse) do={:log info "user header= $udh"}
:while ([:typeof $udh] = "str") do={
# извлекаем тип пользовательского заголовка(блок)
:set $udhIEI [:pick $udh 0 2]
:if ($debugPduParse) do={:log info "Information-Element-Identifier= $udhIEI"}
# извлекаем длинну пользовательского заголовка(блок)
:set $udhLIE [:tonum ("0x".[:pick $udh 2 4])]
:if ($debugPduParse) do={:log info "Length of Information-Element= $udhLIE"}
# извлекаем пользовательский заголовок(блок)
:set $udhIE [:pick $udh 4 (4 + $udhLIE * 2)]
:if ($debugPduParse) do={:log info "Information-Element= $udhIE"}
# считаем количество извлечённых данных
:set ($structUdh->"size") (($structUdh->"size") + 2 + $udhLIE)
# извлекаем в структуру блок заголовка
:if ([:typeof [:find $structUdh $udhIEI]] != num) do={:set $structUdh ($structUdh , [[:parse "({\"$udhIEI\"=\"$udhIE\"})"]])
} else={:set ($structUdh->$udhIEI) $udhIE}
:if ($udhIEI = "00") do={
:set ($structUdh->"concatenated") true
:if ([:len $udhIE] != 6) do={:set $retArg "Error parse, wrong length in concatenated block\r\n"; throw;}
:set $udhOctet1 [:tonum ("0x".[:pick $udhIE 0 2])]
:set $udhOctet2 [:tonum ("0x".[:pick $udhIE 2 4])]
:if ($udhOctet2 = 0) do={:set $retArg "Error parse, wrong size SMS in concatenated block\r\n"; throw;}
:set $udhOctet3 [:tonum ("0x".[:pick $udhIE 4 6])]
:if (($udhOctet3 = 0) or ($udhOctet3 > $udhOctet2)) do={:set $retArg "Error parse, present curent value in concatenated block\r\n"; throw;}
:if ($debugPduParse) do={:log info "Concatenated short message reference number= $udhOctet1"}
:if ($debugPduParse) do={:log info "Maximum number of short messages in the concatenated short message= $udhOctet2"}
:if ($debugPduParse) do={:log info "Sequence number of the current short message= $udhOctet3"}
}
:if ($udhIEI = "08") do={
:set ($structUdh->"concatenated") true
:if ([:len $udhIE] != 8) do={:set $retArg "Error parse, wrong length in concatenated block\r\n"; throw;}
:set $udhOctet1 [:tonum ("0x".[:pick $udhIE 0 4])]
:set $udhOctet2 [:tonum ("0x".[:pick $udhIE 4 6])]
:if ($udhOctet2 = 0) do={:set $retArg "Error parse, wrong size SMS in concatenated block\r\n"; throw;}
:set $udhOctet3 [:tonum ("0x".[:pick $udhIE 6 8])]
:if (($udhOctet3 = 0) or ($udhOctet3 > $udhOctet2)) do={:set $retArg "Error parse, present curent value in concatenated block\r\n"; throw;}
:if ($debugPduParse) do={:log info "Concatenated short message reference number= $udhOctet1"}
:if ($debugPduParse) do={:log info "Maximum number of short messages in the concatenated short message= $udhOctet2"}
:if ($debugPduParse) do={:log info "Sequence number of the current short message= $udhOctet3"}
}
# обрезаем строку пользовательского заголовка
:set $udh [:pick $udh ($udhLIE * 2 + 4) [:len $udh]]
}
}
# извлекаем блок текста из PDU
:if ($udhi) do={:set $udText [:pick $tpduLine (28 + $oaLen + $udhl * 2) [:len $tpduLine]]} else={
:set $udText [:pick $tpduLine (26 + $oaLen) [:len $tpduLine]]}
:if ($debugPduParse) do={:log info "udText= $udText"}
# набиваем структуру sms
:set $retArg {"replyPath"=$replyPath;"udhi"=$udhi;"sri"=$sri;"mms"=$mms;"oaLen"=$oaLen;"oaLine"=$oaLine;"pid"=$pid;"dcsModel"=$dcsModel;"compressedUd"=$compressedUd;"dcsFlagClass"=$dcsFlagClass;"dcsCode"=$dcsCode;"dcsClass"=$dcsClass;"dcsSubModel"=$dcsSubModel;"dcsMwig"=$dcsMwig;"dcsMwigIndicationType"=$dcsMwigIndicationType;"dcsMwigIndication"=$dcsMwigIndication;"sctsLine"=$sctsLine;"udl"=$udl;"structUdh"=$structUdh;"udhOctet1"=$udhOctet1;"udhOctet2"=$udhOctet2;"udhOctet3"=$udhOctet3;"udText"=$udText}
#					      bool              bool        bool       bool         num              str          num              num                      bool                         bool                   num                 num                                                                                                                                                      str
} on-error={:set $errorFlagParse true}
:return $retArg
}

содержимое functionPDU

Код: Выделить всё

:global errorFlagParse false;

###########функция конвертации 7bit в UTF-8 текст##########################
# Работает, основываясь на GSM 03.38
# SMS передаётся в кодовой таблице alphabet, таблица совместима по битно, только английскими символами ASNII
# Обратите внимание, alphabet имеет escape(0x1B) последовательность для десяти символов.
# В функцию надо передать аргумент с именем "instring”
# аргумент iter8 заставляет начать отсчёт с восьмой итерации

:global convert7bitToUtf8 do={ 
	:global errorFlagParse;
	:local alphabetToUtf8 {0="\40";1="\C2\A3";2="\24";3="\C2\A5";4="\C3\A8";5="\C3\A9";6="\C3\B9";7="\C3\AC";8="\C3\B2";9="\C3\87";10="\0A";11="\C3\98";12="\C3\B8";13="\0D";14="\C3\85";15="\C3\A5";16="\CE\94";17="\5F";18="\CE\A6";19="\CE\93";20="\CE\9B";21="\CE\A9";22="\CE\A0";23="\CE\A8";24="\CE\A3";25="\CE\98";26="\CE\9E";28="\C3\86";29="\C3\A6";30="\C3\9F";31="\C3\89";32="\20";33="\21";34="\22";35="\23";36="\C2\A4";37="\25";38="\26";39="\27";40="\28";41="\29";42="\2A";43="\2B";44="\2C";45="\2D";46="\2E";47="\2F";48="\30";49="\31";50="\32";51="\33";52="\34";53="\35";54="\36";55="\37";56="\38";57="\39";58="\3A";59="\3B";60="\3C";61="\3D";62="\3E";63="\3F";64="\C2\A1";65="\41";66="\42";67="\43";68="\44";69="\45";70="\46";71="\47";72="\48";73="\49";74="\4A";75="\4B";76="\4C";77="\4D";78="\4E";79="\4F";80="\50";81="\51";82="\52";83="\53";84="\54";85="\55";86="\56";87="\57";88="\58";89="\59";90="\5A";91="\C3\84";92="\C3\96";93="\C3\91";94="\C3\9C";95="\C2\A7";96="\C2\BF";97="\61";98="\62";99="\63";100="\64";101="\65";102="\66";103="\67";104="\68";105="\69";106="\6A";107="\6B";108="\6C";109="\6D";110="\6E";111="\6F";112="\70";113="\71";114="\72";115="\73";116="\74";117="\75";118="\76";119="\77";120="\78";121="\79";122="\7A";123="\C3\A4";124="\C3\B6";125="\C3\B1";126="\C3\BC";127="\C3\A0";3466="\0C";3476="\5E";3496="\7B";3497="\7D";3503="\5C";3516="\5B";3517="\7E";3518="\5D";3520="\7C";3557="\E2\82\AC";};
	:local curbit 0;
	:if ($iter8) do={:set $curbit 6;}
	:local nextpart 0;
	:local escape false;
	:local decodedLine "";
	do {
	:if (([:len $instring] % 2) != 0) do={:set $decodedLine "incomplete number of bytes in function convert7bitToUtf8"; throw;} 
	:for curposition from=0 to=([:len $instring] - 1) step=2 do={
		:local charcode [:tonum ("0x".[:pick $instring $curposition ($curposition +2)])];
		:local tmp ($charcode & (127>>$curbit));
		:set $tmp ($tmp<<$curbit);
		:set $tmp ($tmp + $nextpart);
		:set $nextpart ($charcode>>(7-$curbit));
		:set curbit ($curbit+1);
		:if ($tmp = 27) do={:set $escape true;} else={
			:if ($escape) do={:local tmp2 ($alphabetToUtf8->[:tostr (3456 | $tmp)]);
				:if ([:len $tmp2] = 0) do={:set $decodedLine "unknown character in function convert7bitToUtf8"; throw;};
				:set $decodedLine ($decodedLine.$tmp2);
				:set $escape false;
				} else={:set $decodedLine ($decodedLine.($alphabetToUtf8->[:tostr $tmp]));};};
		:if ($curbit = 7) do={
			:set $tmp $nextpart;
		:if ($tmp = 27) do={:set $escape true;} else={
			:if ($escape) do={:local tmp2 ($alphabetToUtf8->[:tostr (3456 | $tmp)]);
				:if ([:len $tmp2] = 0) do={:set $decodedLine "unknown character in function convert7bitToUtf8"; throw;};
				:set $decodedLine ($decodedLine.$tmp2);
				:set $escape false;
				} else={:set $decodedLine ($decodedLine.($alphabetToUtf8->[:tostr $tmp]));};};
			:set $curbit 0;
			:set $nextpart 0;
		};
	};
	:if ($iter8) do={:set $decodedLine [:pick $decodedLine 1 [:len $decodedLine]];}
	# оговорено, что в sms при окончании на восьмом символе заполнителем являются нули, в ussd заполнитель CR(при указании в конце CR символы надо за двоить), заполнитель в номере телефона не нашёл
	:if (($curbit = 0) and ([:pick $decodedLine ([:len $decodedLine] - 1)] = "\40")) do={
	:set $decodedLine [:pick $decodedLine 0 ([:len $decodedLine] - 1)];}
	:if ([:len $decodedLine] = 0) do={:set $decodedLine "Error in parsing: function malfunction convert7bitToUtf8\r\n"; throw;}
	} on-error={:set $errorFlagParse true;}
	:return $decodedLine;
};
########## Конец функции ###################################################

:global convert8bitToUtf8 do={
# кодировка спецификацией не оговорена и определяется пользователем, пишем сами для своих целей.
}

:global symbolsHex {"\00";"\01";"\02";"\03";"\04";"\05";"\06";"\07";"\08";"\09";"\0A";"\0B";"\0C";"\0D";"\0E";"\0F";"\10";"\11";"\12";"\13";"\14";"\15";"\16";"\17";"\18";"\19";"\1A";"\1B";"\1C";"\1D";"\1E";"\1F";"\20";"\21";"\22";"\23";"\24";"\25";"\26";"\27";"\28";"\29";"\2A";"\2B";"\2C";"\2D";"\2E";"\2F";"\30";"\31";"\32";"\33";"\34";"\35";"\36";"\37";"\38";"\39";"\3A";"\3B";"\3C";"\3D";"\3E";"\3F";"\40";"\41";"\42";"\43";"\44";"\45";"\46";"\47";"\48";"\49";"\4A";"\4B";"\4C";"\4D";"\4E";"\4F";"\50";"\51";"\52";"\53";"\54";"\55";"\56";"\57";"\58";"\59";"\5A";"\5B";"\5C";"\5D";"\5E";"\5F";"\60";"\61";"\62";"\63";"\64";"\65";"\66";"\67";"\68";"\69";"\6A";"\6B";"\6C";"\6D";"\6E";"\6F";"\70";"\71";"\72";"\73";"\74";"\75";"\76";"\77";"\78";"\79";"\7A";"\7B";"\7C";"\7D";"\7E";"\7F";"\80";"\81";"\82";"\83";"\84";"\85";"\86";"\87";"\88";"\89";"\8A";"\8B";"\8C";"\8D";"\8E";"\8F";"\90";"\91";"\92";"\93";"\94";"\95";"\96";"\97";"\98";"\99";"\9A";"\9B";"\9C";"\9D";"\9E";"\9F";"\A0";"\A1";"\A2";"\A3";"\A4";"\A5";"\A6";"\A7";"\A8";"\A9";"\AA";"\AB";"\AC";"\AD";"\AE";"\AF";"\B0";"\B1";"\B2";"\B3";"\B4";"\B5";"\B6";"\B7";"\B8";"\B9";"\BA";"\BB";"\BC";"\BD";"\BE";"\BF";"\C0";"\C1";"\C2";"\C3";"\C4";"\C5";"\C6";"\C7";"\C8";"\C9";"\CA";"\CB";"\CC";"\CD";"\CE";"\CF";"\D0";"\D1";"\D2";"\D3";"\D4";"\D5";"\D6";"\D7";"\D8";"\D9";"\DA";"\DB";"\DC";"\DD";"\DE";"\DF";"\E0";"\E1";"\E2";"\E3";"\E4";"\E5";"\E6";"\E7";"\E8";"\E9";"\EA";"\EB";"\EC";"\ED";"\EE";"\EF";"\F0";"\F1";"\F2";"\F3";"\F4";"\F5";"\F6";"\F7";"\F8";"\F9";"\FA";"\FB";"\FC";"\FD";"\FE";"\FF"};


:global convertUcs2ToUtf8 do={
	:local decodedLine "";
	:global symbolsHex;
	:for curposition from=0 to=([:len $instring] -1) step=4 do={
	:local i [:tonum ("0x".[:pick $instring $curposition ($curposition +4)])];
	:if ($i < 0x80) do={
	:set $decodedLine ($decodedLine.($symbolsHex->$i));
	};
	:if (($i >= 0x80) and ($i < 0x800)) do={
	:local byteA (($i >> 6) | 192);
	:local byteB (($i & 63) | 128);
	:set $decodedLine ($decodedLine.($symbolsHex->$byteA).($symbolsHex->$byteB));
	};
	:if ($i >= 0x800) do={
	:local byteA (($i >> 12) | 224);
	:local byteB ((($i >> 6) & 63) | 128);
	:local byteC (($i & 63) | 128);
	:set $decodedLine ($decodedLine.($symbolsHex->$byteA).($symbolsHex->$byteB).($symbolsHex->$byteC));
	};
	};
		:return $decodedLine;
};

:global convertAddress do={
	:local decodedLine "";
	:global errorFlagParse;
	:global convert7bitToUtf8;
	:local typeOfAddress [:tonum ("0x".[:pick $instring 0 2])];
# Type-of-number
# 0 0 0 Unknown
# 0 0 1 International number
# 0 1 0 National number
# 0 1 1 Network specific number
# 1 0 0 Subscriber number
# 1 0 1 Alphanumeric, (coded according to GSM TS 03.38 7-bit default alphabet)
# 1 1 0 Abbreviated number
# 1 1 1 Reserved for extension
	:local typeOfNumber (($typeOfAddress >> 4) & 7);
	:local nameNumber {"Unknown";"International number";"National number";"Network specific number";"Subscriber number";"Alphanumeric";"Abbreviated number";"Reserved for extension"};
# Numbering-plan-identification (applies for Type-of-number = 000,001,010)
# 0 0 0 0 Unknown
# 0 0 0 1 ISDN/telephone numbering plan (E.164/E.163)
# 0 0 1 1 Data numbering plan (X.121)
# 0 1 0 0 Telex numbering plan
# 1 0 0 0 National numbering plan
# 1 0 0 1 Private numbering plan
# 1 0 1 0 ERMES numbering plan (ETSI DE/PS 3 01-3)
# 1 1 1 1 Reserved for extension
# All other values are reserved.
	:local numberingPlanIdentification ($typeOfAddress & 15);
	:local namePlan {"Unknown";"ISDN/telephone numbering plan";"Data numbering plan";"Reserved";"Telex numbering plan";"Reserved";"Reserved";"Reserved";"National numbering plan";"Private numbering plan";"ERMES numbering plan";"Reserved";"Reserved";"Reserved";"Reserved";"Reserved for extension"};
	:local addressValue [:pick $instring 2 [:len $instring]];
	:local size [:len $addressValue];
	:local rotare "";
# BCD number
# 1010 *
# 1011 #
# 1100 a
# 1101 b
# 1110 c
# 1111 fill bits
	:local bcd {"A"="*";"B"="#";"C"="a";"D"="b";"E"="c"};
	do {
		:if (($typeOfAddress >> 7) != 1) do={
			:set $decodedLine "Error in parsing numbers: 7 bits in Type-of-Address not set to 1\r\n";
			throw;};
		:if (($typeOfNumber = 3) or ($typeOfNumber = 4)  or ($typeOfNumber = 6) or ($typeOfNumber = 7)) do={
			:set $decodedLine ("Error in parsing numbers: unsupported number type - ".($nameNumber->$typeOfNumber)."\r\n");
			throw;};
		:if ((($typeOfNumber = 0) or ($typeOfNumber = 1)  or ($typeOfNumber = 2)) and ($numberingPlanIdentification != 1)) do={
			:set $decodedLine ("Error in parsing numbers: unsupported number type - ".($nameNumber->$typeOfNumber)." and Numbering-plan-identification - ".($namePlan->$numberingPlanIdentification)."\r\n");
			throw;};
		:if (($typeOfNumber = 5) and ($numberingPlanIdentification != 0)) do={
			:set $decodedLine ("Error in parsing numbers: unsupported number type - ".($nameNumber->$typeOfNumber)." and Numbering-plan-identification - ".($namePlan->$numberingPlanIdentification)."\r\n");
			throw;};
		:if (($size % 2) = 1) do={
			:set $decodedLine "Error in parsing numbers: length is not equal to byte\r\n";
			throw;};
		:if (($typeOfNumber = 0) or ($typeOfNumber = 1)  or ($typeOfNumber = 2)) do={
		:for i from=1 to=$size do={
			:if (($i%2) = 0) do={:set $rotare ($rotare.[:pick $addressValue ($i - 2)]);
			} else={:set $rotare ($rotare.[:pick $addressValue $i]);};}
		:set $addressValue "";
		:for i from=0 to=($size - 1) do={
			:local single [:pick $rotare $i];
			:if ([:tonum ("0x".$single)] <= 9) do={:set $addressValue ($addressValue.$single);
			} else={:if (($single = "F") and ($i = ($size - 1))) do={} else={:if ($single != "F") do={
			:set $addressValue ($addressValue.($bcd->$single));} else={
			:set $decodedLine "Error in parsing numbers: septet of adding to byte is not at the end\r\n";
			throw;
		}}}}
		:if ($typeOfNumber = 1) do={:set $decodedLine ("+".$addressValue." (".($nameNumber->$typeOfNumber).")");
		} else={:set $decodedLine ($addressValue." (".($nameNumber->$typeOfNumber).")");}
		} else={:if ($typeOfNumber = 5) do={:set $addressValue [$convert7bitToUtf8 instring=$addressValue];
		:set $decodedLine ($addressValue." (".($nameNumber->$typeOfNumber).")");}};
		:if ([:len $decodedLine] = 0) do={:set $decodedLine "Error in parsing numbers: function malfunction convertAddress\r\n"; throw;}
	} on-error={:set $errorFlagParse true;}
	:return $decodedLine;
};

:global convertBodyPDU do={
	:global errorFlagParse;
	:global convert7bitToUtf8;
	:global convert8bitToUtf8;
	:global convertUcs2ToUtf8;
	:local outstring;
	:if ($typeFormat = 0) do={:set outstring [$convert7bitToUtf8 instring=$instring iter8=$iter8];
	} else={:if ($typeFormat = 1) do={:set outstring [$convert8bitToUtf8 instring=$instring];
	} else={:if ($typeFormat = 2) do={:set outstring [$convertUcs2ToUtf8 instring=$instring];}}}
	:if ([:len $outstring] = 0) do={:set $outstring "Error in parsing body string: function malfunction convertBodyPDU\r\n";
	:set $errorFlagParse true;}
	:return $outstring;

};

:global convertScts do={ 
:global errorFlagParse;
:global UnixTimeToFormat;
:local decodedLine "";
do {
:local tmp [:tonum ("0x".[:pick $sctsLine 0])]
:local byteY [:tonum ("0x".[:pick $sctsLine 1])]
:if (($tmp > 9) or ($byteY > 9)) do={:set $decodedLine "Error parse in function convertScts, year\r\n"; throw;}
:set $byteY ($byteY * 10 + $tmp)
:set $tmp [:tonum ("0x".[:pick $sctsLine 2])]
:local byteMn [:tonum ("0x".[:pick $sctsLine 3])]
:if (($tmp > 9) or ($byteMn > 1)) do={:set $decodedLine "Error parse in function convertScts, monats\r\n"; throw;}
:set $byteMn ($byteMn * 10 + $tmp)
:if ($byteMn > 12) do={:set $decodedLine "Error parse in function convertScts, monats\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 4])]
:local byteD [:tonum ("0x".[:pick $sctsLine 5])]
:if (($tmp > 9) or ($byteD > 3)) do={:set $decodedLine "Error parse in function convertScts, days\r\n"; throw;}
:set $byteD ($byteD * 10 + $tmp)
:if ($byteD > 31) do={:set $decodedLine "Error parse in function convertScts, days\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 6])]
:local byteH [:tonum ("0x".[:pick $sctsLine 7])]
:if (($tmp > 9 ) or ($byteH > 2)) do={:set $decodedLine "Error parse in function convertScts, hours\r\n"; throw;}
:set $byteH ($byteH * 10 + $tmp)
:if ($byteH > 23) do={:set $decodedLine "Error parse in function convertScts, hours\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 8])]
:local byteM [:tonum ("0x".[:pick $sctsLine 9])]
:if (($tmp > 9) or ($byteM > 5)) do={:set $decodedLine "Error parse in function convertScts, minutes\r\n"; throw;}
:set $byteM ($byteM * 10 + $tmp)
:if ($byteM > 59) do={:set $decodedLine "Error parse in function convertScts, minutes\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 10])]
:local byteS [:tonum ("0x".[:pick $sctsLine 11])];
:if (($tmp > 9) or ($byteS > 5)) do={:set $decodedLine "Error parse in function convertScts, seconds\r\n"; throw;}
:set $byteS ($byteS * 10 + $tmp)
:if ($byteS > 59) do={:set $decodedLine "Error parse in function convertScts, seconds\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 12])]
:local byteO [:tonum ("0x".[:pick $sctsLine 13])];
:if ($tmp > 9) do={:set $decodedLine "Error parse in function convertScts, offset\r\n"; throw;}
:if (($byteO >> 3) = 1) do={:set $byteO ((0 - (($byteO & 7) * 10 + $tmp)) * 900)} else={:set $byteO ((($byteO & 7) * 10 + $tmp) * 900)}
:local months;
:if (($byteY % 4) = 0) do={
        :set months [:toarray (0,31,60,91,121,152,182,213,244,274,305,335)]
} else={:set months [:toarray (0,31,59,90,120,151,181,212,243,273,304,334)]}
:local unixTime (($byteY * 365 + ($byteY - 1) / 4 + ($months->($byteMn - 1)) + $byteD) * 86400)
:set $unixTime ($byteH * 3600 + $byteM * 60 + $byteS + $unixTime + 946684800)
:local gmt false
:if ([:typeof ($timeStruct->"gmt")] = "bool") do={:set $gmt ($timeStruct->"gmt")}
:local offsetR [/system clock get gmt-offset]
:if (($offsetR >> 31) = 1) do={:set $offsetR ($offsetR - 4294967296); :set $offset ($offset * -1)}
:if ($gmt) do={:set $tmp ($unixTime + $byteO)} else={:set $tmp $unixTime}
:local hd "Date: "
:if ([:typeof $headerDate] = "str") do={:set $hd $headerDate}
:set $tmp [$UnixTimeToFormat timeStamp=$tmp timeStruct=$timeStruct]
:if ([:len $tmp] = 0) do={:set $decodedLine "Error parse in function convertScts, no return from UnixTimeToFormat\r\n"; throw;}
:set $decodedLine ($hd.$tmp)
:if ($gmt) do={:local numO
	:if ($byteO >= 0) do={:set $decodedLine ($decodedLine." -"); :set $numO $byteO} else={:set $decodedLine ($decodedLine." +"); :set $numO ($byteO * -1)}
	:set $decodedLine ($decodedLine.[:tostr ($numO / 3600)])
	:set $numO (($numO % 3600) / 60)
	:if ($numO != 0) do={:set $decodedLine ($decodedLine.":".[:tostr $numO])}
	:set $decodedLine ($decodedLine." GMT")}
:if ($byteO = $offsetR) do={:set $decodedLine ($decodedLine."\r\n")} else={
:set $decodedLine ($decodedLine." (SMS time)\r\n")
:if (!$gmt) do={:set $tmp ($unixTime + $byteO - $offsetR)} else={:set $tmp ($unixTime + $byteO)}
:set $tmp [$UnixTimeToFormat timeStamp=$tmp timeStruct=$timeStruct]
:if ([:len $tmp] = 0) do={:set $decodedLine "Error parse in function convertScts, no return from UnixTimeToFormat\r\n"; throw;}
:set $decodedLine ($decodedLine.$hd.$tmp)
:if ($gmt) do={:local numO
	:if ($offsetR >= 0) do={:set $decodedLine ($decodedLine." -"); :set $numO $offsetR} else={:set $decodedLine ($decodedLine." +"); :set $numO ($offsetR * -1)}
	:set $decodedLine ($decodedLine.[:tostr ($numO / 3600)])
	:set $numO (($numO % 3600) / 60)
	:if ($numO != 0) do={:set $decodedLine ($decodedLine.":".[:tostr $numO])}
	:set $decodedLine ($decodedLine." GMT")}
:set $decodedLine ($decodedLine." (OS time) \r\n")
}
} on-error={:set $errorFlagParse true;}
:if ([:len $decodedLine] = 0) do={:set $decodedLine "Error in parsing date string: function malfunction convertScts\r\n"
	:set $errorFlagParse true}
:return $decodedLine;
}

# Считаются только unsigned
:global UnixTimeToFormat do={
:local decodedLine ""
:local before false
:if ($timeStamp < 0) do={:set $before true; :set $timeStamp ($timeStamp * -1)}
:local timeS ($timeStamp % 86400)
:local timeH ($timeS / 3600)
:local timeM ($timeS % 3600 / 60)
:set $timeS ($timeS - $timeH * 3600 - $timeM * 60)

:local dateD ($timeStamp / 86400)
:local dateM 2
:local dateY 1970
:local leap false
:while (($dateD / 365) > 0) do={
:set $dateD ($dateD - 365)
:set $dateY ($dateY + 1)
:set $dateM ($dateM + 1) 
:if ($dateM = 4) do={:set $dateM 0
:if (($dateY % 400 = 0) or ($dateY % 100 != 0)) do={:set $leap true
:set $dateD ($dateD - 1)}} else={:set $leap false}}
:local months [:toarray (0,31,28,31,30,31,30,31,31,30,31,30,31)]
:if (leap) do={:set $dateD ($dateD + 1); :set ($months->2) 29}
do {
:for i from=1 to=12 do={:if (($months->$i) > $dateD) do={:set $dateM $i; :set $dateD ($dateD + 1); break;} else={:set $dateD ($dateD - ($months->$i))}}
} on-error={}

:local tmod 2
:local s "."
:local nf true
:local mstr {"jan";"feb";"mar";"apr";"may";"jun";"jul";"aug";"sep";"oct";"nov";"dec"}
:if ([:typeof $timeStruct] = "array") do={
:if ([:typeof ($timeStruct->"timeFormat")] = "num") do={:set $tmod ($timeStruct->"timeFormat")}
:if ([:typeof ($timeStruct->"dateSeparator")] = "str") do={:set $s ($timeStruct->"dateSeparator")}
:if ([:typeof ($timeStruct->"numFill")] = "bool") do={:set $nf ($timeStruct->"numFill")}
:if (([:typeof ($timeStruct->"monthsStr")] = "array") and ([:len ($timeStruct->"monthsStr")] = 12)) do={:set $mstr ($timeStruct->"monthsStr")}
}
:local strY [:tostr $dateY]
:local strMn
:local strD
:local strH
:local strM
:local strS
:if ($nf) do={
:if ($dateM > 9) do={:set $strMn [:tostr $dateM]} else={:set $strMn ("0".[:tostr $dateM])}
:if ($dateD > 9) do={:set $strD [:tostr $dateD]} else={:set $strD ("0".[:tostr $dateD])}
:if ($timeH > 9) do={:set $strH [:tostr $timeH]} else={:set $strH ("0".[:tostr $timeH])}
:if ($timeM > 9) do={:set $strM [:tostr $timeM]} else={:set $strM ("0".[:tostr $timeM])}
:if ($timeS > 9) do={:set $strS [:tostr $timeS]} else={:set $strS ("0".[:tostr $timeS])}
} else={
:set strMn [:tostr $dateM]
:set strD [:tostr $dateD]
:set strH [:tostr $timeH]
:set strM [:tostr $timeM]
:set strS [:tostr $timeS]
}
do {
:if ($tmod = 1) do={:set $decodedLine "$strY$s$strMn$s$strD $strH:$strM:$strS"; break;}
:if ($tmod = 2) do={:set $decodedLine "$strD$s$strMn$s$strY $strH:$strM:$strS"; break;}
:if ($tmod = 3) do={:set $decodedLine ("$strD ".($mstr->($dateM - 1))." $strY $strH:$strM:$strS"); break;}
:if ($tmod = 4) do={:set $decodedLine ("$strY ".($mstr->($dateM - 1))." $strD $strH:$strM:$strS"); break;}
} on-error={}
:return $decodedLine;
}

:global sendMailUTF8 do={
# функция должна возвращать булевое истина при успешной отправке сообщения
/tool e-mail send to=$emailAdr subject="$head\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8" body=$emailBody
:local sendit 120
:while ($sendit > 0) do={:delay 1s; :set $sendit ($sendit - 1)
:if (([tool e-mail get last-status] = "succeeded") or ([tool e-mail get last-status] = "failed")) do={:set $sendit 0}}
:if ([tool e-mail get last-status] = "succeeded") do={:return true} else={:return false}
}

:global saveFile do={
:if ([/file find name~"$nameFile"] = "") do={/file print file="$nameFile"
:while ([/file find name~"$nameFile"] = "") do={:delay 1s}}
do {/file set $nameFile contents=$bodyFile} on-error={}
}

:global exitFunctionPDU do={
:global errorFlagParse
:global convert7bitToUtf8
:global convert8bitToUtf8
:global convertUcs2ToUtf8
:global convertAddress
:global convertBodyPDU
:global convertScts
:global UnixTimeToFormat
:global sendMailUTF8
:global extractSmsModem
:global saveFile
:global symbolsHex
:set $symbolsHex
:set $saveFile
:set $errorFlagParse
:set $convert7bitToUtf8
:set $convert8bitToUtf8
:set $convertUcs2ToUtf8
:set $convertAddress
:set $convertBodyPDU
:set $convertScts
:set $UnixTimeToFormat
:set $extractSmsModem
:set $sendMailUTF8
:global exitFunctionPDU
:set $exitFunctionPDU
}
Имена дочерних скриптов не меняем, т.к. они вызываются по потребности из главного.
Скрипт с функцией извлечения “extractSmsModem” подлежит редактированию под конкретный модем. Изучайте список AT команд для своего конкретного модема. Текущий представленный образец работает с LTE и PPP интерфейсами, поддерживаемыми AT команды по умолчанию.
В главном скрипте в начале содержится список переменных для настройки поведения скрипта.
Теперь для тех кто любит извращения – дело в том, что в PDU могут присутствовать разные флаги, задающие поведение sms. Например, флаг class, который может указать – не сохранять sms в память, а отобразить на дисплее. В случае отсутствия дисплея, стандартом оговорено - принимающая сторона сама решает поведение такого sms. Тоже самое происходит и с CBM(cell broadcast message), если модем способен их обрабатывать. Для тех кто не знает, это особый вид PDU, для многоадресной рассылки, например – сеть оператора может передаёт информацию о наступающей грозе в конкретной местности (пора выдирать роутер из розетки). Так вот по факту, модемы просто вываливают подобные PDU в терминал и забывают о них, а ROS просто их игнорирует.
Вариантов не много – постоянно мониторить. Для тех модемов, у которых есть свободный порт с терминалом, надо обрабатывать каждое сообщение от модема и в случаи нахождения соответствие нужного идентификатора, обрабатывать сообщения. Второй способ более универсальный и подходит для всех модемов у которых есть терминал (LTE и т.д.) – надо включить логирование обмена ROS с модемом (/system loging -> lte, ppp, и т.д.) и дальше парсить уже этот лог на предмет принятых PDU.
P.S. В скрипте встроена поддержка, если вы её не отключите, то я наберу примеры для дополнения не поддерживаемых функции и реализую их. Отправка сработает, только, если главный цикл получит sms и не сможет разобрать его. (не все концепции оговоренные в 3GPP у меня поддерживаются из-за отсутствия образцов) . Если есть проблеммы с извлечением из модема, то ни кому ни чего не отправляется.
За время отладки, только от одного пользователя пришли отладочные конверты. Они и были добавлены в скрипт. По сему в данный момент считаю, что проблем ни у кого с парсингом не возникает.

P.S по глюкам смотрим здесь
Последний раз редактировалось pepelxl 29 мар 2022, 19:23, всего редактировалось 28 раз.


Аватара пользователя
podarok66
Модератор
Сообщения: 4355
Зарегистрирован: 11 фев 2012, 18:49
Откуда: МО

Ого! Работка-то проделана немалая... Спасибо. Сам бы поставил, но не пользуюсь модемами, в МО интернет скоро под каждый куст подведут :-)


Мануалы изучил и нигде не ошибся? Фаервол отключил? Очереди погасил? Витая пара проверена? ... Тогда Netinstal'ом железку прошей и настрой ее заново. Что, все равно не фурычит? Тогда к нам. Если не подскажем, хоть посочувствуем...
Аватара пользователя
podarok66
Модератор
Сообщения: 4355
Зарегистрирован: 11 фев 2012, 18:49
Откуда: МО

Слушайте. А вот возник вопрос.
А нельзя по подобному принципу прикрутить выполнение команд из тела sms?


Мануалы изучил и нигде не ошибся? Фаервол отключил? Очереди погасил? Витая пара проверена? ... Тогда Netinstal'ом железку прошей и настрой ее заново. Что, все равно не фурычит? Тогда к нам. Если не подскажем, хоть посочувствуем...
pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

конечно можно, в самом стандарте PDU заложен терминальный и командный режим. Наверно даже можно откопать соответствующие документы 3GPP со стандартами. Вот только sms у нас были и есть платные, расточительно.


Аватара пользователя
podarok66
Модератор
Сообщения: 4355
Зарегистрирован: 11 фев 2012, 18:49
Откуда: МО

pepelxl писал(а): 31 май 2020, 12:59 конечно можно, в самом стандарте PDU заложен терминальный и командный режим. Наверно даже можно откопать соответствующие документы 3GPP со стандартами. Вот только sms у нас были и есть платные, расточительно.
Деньги - это агрумент.
С другой стороны, часть объектов у нас бывает расположена хз где. И ехать к ним, чтобы ввести пару команд значительно расточительнее. Но тут уж кому что удобнее по потребностям. Я от критичных железок всегда держу туннель на сервер с Debian. Расходы по аренде - в разные годы от 100 до 500 рублей. Зато я всегда могу на них попасть в поковырять. Или из дома, или даже со смарта. Особенно актуально всё это оказывается в праздники или в три часа ночи в непогоду (закон подлости работает всегда). Я просто поинтересовался возможностями. И описал своё видение ситуации.
В любом случае, спасибо вам за щедрость.


Мануалы изучил и нигде не ошибся? Фаервол отключил? Очереди погасил? Витая пара проверена? ... Тогда Netinstal'ом железку прошей и настрой ее заново. Что, все равно не фурычит? Тогда к нам. Если не подскажем, хоть посочувствуем...
pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

В любом случае скрипт ломается пополам после цикла чтения sms, образуется массив со всеми запчастями pdu. далее с ним можно делать что угодно.
Но мне не понятен сакральный смысл в управлении роутером при встроенной штатной функции , если только автор не приверженец теории конспиралогии.
А так , к примеру, nokia получала рингтоны и wap в 8bit + header, ничто не мешает принять такое sms и проиграть на встроенном биппере. :-)


pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

Обновлён скрипт PDUtoEmail
Поправлено:
1- неверное выбрасывание исключения если извлечено, только неполное SMS
2- потеря блоков sms ожидающих сборки от прошлой итерации цикла если в текущей итерации ничего не приходит или вываливается исключение на парсинг или от функции извлечения.

Вообще с сохранением блоков горбуха получается. Надо покумекать как сделать лучше. Сохранение внутри цикла не получится сделать(от этого я ушёл, но получил то что сейчас имею), т.к. при соблюдении трёх условий(наличие неполного sms, невозможности отправки email, и опции incompleteSave true) получал циклическую итерацию сохранений). Если кратко, то закончились бабки на балансе -> пришла от оператора sms о недостатке средств -> не уместилась в модеме -> скрипт запорол флешку роутера.
Позже попробую изменить весь принцип или завернуть в отдельную функцию сохранения.


pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

Обновлена логика скрипта
extractSmsModem - добавлено обязательный возврат при действии "Clear" для защиты флешь памяти OS
PDUtoEMAIL - глобально изменена логика (стала более "читаема"), теперь, при не возможности отправить sms, скрипт сохраняет содержимое на флешь. как только интернет станет доступен, всё отправится на почту.
functionPDU- вынесены некоторые действия из главного скрипта в функции. В том числе функция sendMailUTF8, если вам нужно отправить сообщение в другое место (например в телегу), то измените эту функцию, однако, профиль email всё-равно должен быть заполнен для отладки


shurik.volkov
Сообщения: 3
Зарегистрирован: 14 июн 2020, 12:00

Привет. Подскажи пожалуйста почему скрипт не хочет отправлять сообщения на почту.
Симптомы такие: в PDUtoEMAIL включил debugPduParse и он каждый раз присылал пустые значения, понял на основе других скриптов что в extractSmsModem нужно добавить wait=yes чтобы была задержка. После этого скрипт начал читать сообщения. В логи сыпет отладочную информацию. Но ни в какую не хочет отправлять на почту. Профиль заполнен, из терминала через /tool e-mail send сообщения отправляются, а из скрипта никак. И было упоминание что можно слать в телеграмм, что нужно изменить чтобы можно было перейти с почты на телегу. Спасибо


pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

Отключите debug
Проверьте , что в скрипте правильно заполнен адрес email.
После запуска скрипта в логе должно появиться, что отправлено/не отправлено сообщение на мыло. Если записи нет, то скрипт не доходит до отправки сообщения.
Какое оборудование, какой модем?


Ответить