Доделал русифицированную отправку.
Применение аналогичное.
Спойлер
/*
* pdumsg.h
*
* Created on: Feb 21, 2025
* Author: andycat2013@yandex.ru
*
* модуль работы дла работы с SMS сообщениями в PDU формате
* тестировался на STM32F411CEU6 STM32CubeIDE
*
*/
#ifndef PDUMSG_H_
#define PDUMSG_H_
#include <string.h>
#include <stdio.h>
//#define SHOW_LOG_PDU
unsigned short isValidUTF8len(unsigned char * inStr, const unsigned short inLen); // проверяет строку на коректность, выдает длину в символах, исправляет кривые байты
unsigned char calcBytesInCharUTF8(const unsigned char * inChar);
constexpr unsigned short maxLenMultiMessage = 512; // максимальный размер текста многоблочной СМС, используется для вывода и/или обработки getTextMultyMessage
unsigned char * convertRusUTF8toASCIItranslit(const unsigned char * inUTF8); // Транлитерация строки для отображения в логе не поддерживающим UTF8
void addPduMessageWithLenBody(const unsigned char * inText, const unsigned char inLenBodyHeader); // добавление нового сообщения с длиной, полученной из заголовка SIM800
void deletePduMessage(const unsigned char numMessage); // удаление сообщения с нужным номером // нумерация с нуля
unsigned char countPduMessages(void); // число сообщений
unsigned char * getCentrSMSmessage(const unsigned char numMessage); // вытаскиваем номер центра сообщения из СМС
unsigned char * getSenderSMSmessage(const unsigned char numMessage); // вытаскиваем отправителя из СМС
unsigned char * getTextSMSmessage(const unsigned char numMessage); // вытаскиваем текст из СМС
bool isMultiPart(const unsigned char numMessage); // многоблочное сообщение
bool isFullMessage(const unsigned char numMessage); // многоблочное ссобщение полностью загрузилось
unsigned char * getTextMultyMessage(const unsigned char numMessage); // вытаскиваем текст из многоблочного СМС
void deleteMultiMessage(const unsigned char numMessage); // удаление многоблочного сообщения с нужным номером // по факту удаляет все с нужным ИД
constexpr unsigned char lenSender = 16; // длина строки отправителя/получателя
enum typePDUmessage {messageText7bit = 0, messageUCS2}; // формат текста отправляемого сообщения TP-DCS – TP-Data-Coding-Scheme – Схема кодирования данных.
enum typeMessageClass {classMessageNormal = 0, classMessageFlash}; // класс сообщения, обычное или флэш (всплывающее)
void setSCAnumberMNO(unsigned char * inNumber); // установка номера центра сообщений оператора
void sendUTF8toQueueSMSPDU(const unsigned char * inTextSender,
const typeMessageClass inMessageClass, const unsigned char * inMessageText); // добавить СМС в очередь на отправку
bool existSMStoSendPDU(void); // есть ли что отправлять
unsigned char returnCountBytesMsgPDU(void); // возвращает число байт части сообщения
unsigned char * returnTextMsgPDU(void); // возвращает часть сообщения на отправку
class itemOnePart { // часть сообщения на отправку
private:
public:
itemOnePart (const unsigned char inBytesToOut = 0, const unsigned char * inTextPart = nullptr,
const unsigned short inCountBytesInText = 0) {
this->_countBytesToOut = inBytesToOut;
this->_textPart = new unsigned char [inCountBytesInText + 1];
if (this->_textPart) {
memset(this->_textPart, '\0', inCountBytesInText + 1);
strcpy((char *)this->_textPart, (const char *)inTextPart);
}
this->_nextPart = nullptr;
}
itemOnePart * _nextPart;
unsigned char _countBytesToOut; // число байт в сообщении, то что пишется модему, без учета TP_SCA
unsigned char * _textPart;
};
class sendItemPDU { // класс для одного отправляемого сообщения
private:
typePDUmessage _textFormat;
typeMessageClass _classMSG;
unsigned char _SCAnumber[lenSender];
unsigned char _textSender[lenSender];
unsigned char * _textMessage;
bool _isConverted;
void _convertPDUtoParts(void);
unsigned short _lenInStrByBytes; // длина входящей строки уже в байтах
unsigned short _lenText;
itemOnePart * _firstPart;
unsigned short _lenBytesCalc;
void _convert7bitsToOnePartPDU(void);
void _convertUCS2toOnePartPDU(unsigned short inUTF8chars);
void _convertUCS2toMultiPartsPDU(unsigned short inUTF8chars);
unsigned char * _convertUCStoStringOut(const unsigned char * inPosition, const unsigned char * UTF8, const unsigned char maxOutChars, unsigned char ** outPtr = nullptr);
unsigned char * _addSCA(const unsigned char * inS);
unsigned char * _addByteToStringAs2char(const unsigned char * inS, const unsigned char inByte);
unsigned char * _addDA(const unsigned char * inS);
unsigned char * _convert7bitsToStringOut(const unsigned char * inPosition, const unsigned char * inStringASCII);
void _addPartInMsg (const unsigned char inBytesToOut = 0, const unsigned char * inTextPart = nullptr,
const unsigned short inCountBytesInText = 0) {
if (this->_firstPart) {
itemOnePart * iN = this->_firstPart;
while (iN->_nextPart) {
iN = iN->_nextPart;
}
iN->_nextPart = new itemOnePart (inBytesToOut, inTextPart, inCountBytesInText);
} else {
this->_firstPart = new itemOnePart (inBytesToOut, inTextPart, inCountBytesInText);
}
++this->_countPartsAll;
}
protected:
public:
unsigned char _countPartsAll; // всего сколько частей
unsigned char _countSendParts; // количество частей сообщения отправленных
sendItemPDU * _nextMessage; // указатель на следующее сообщение
sendItemPDU(const unsigned char * inTextSender = nullptr, const typeMessageClass inMessageClass = classMessageNormal, const unsigned char * inMessageText = nullptr,
const unsigned char * inSCAnumber = nullptr); // конструктор создателя сообщения
void deleteTextMessage(void){ // освобождение памяти сообщения
delete this->_textMessage; // освободили память
this->_textMessage = nullptr; // обнулили указатель
// удаляем все части сообщения
if (this->_isConverted) {
while (this->_countPartsAll) {
itemOnePart * nP = this->_firstPart;
while (nP->_nextPart) {
nP = nP->_nextPart;
}
delete nP->_textPart;
nP->_textPart = nullptr;
delete nP;
--this->_countPartsAll;
}
}
}
unsigned char returnCountBytesMsgRaw(void); // возвращает число байт части сообщения
unsigned char * returnTextMsgRaw(void); // возвращает часть сообщения на отправку
};
class sendListPDU { // класс для всех сообщений очереди отправки
private:
unsigned char _countMessageQueue; // размер очереди сообщений к отправке
sendItemPDU * _firstMessage; // указатель на первое сообщение
unsigned char _PDUSCA[lenSender]; // номер центра сообщений
protected:
public:
sendListPDU(void) { // конструктор класса
this->_countMessageQueue = 0; // обнулили количоство сообщенией
this->_firstMessage = nullptr; // обнулили указатель на сообщения
}
void setSCAnumber(unsigned char * inNumber) { // установка номера центра сообщений оператора
memset((unsigned char *)&this->_PDUSCA[0], '\0', lenSender);
strcpy((char *)&this->_PDUSCA[0], (const char *)inNumber);
}
void addUTF8toQueueSMSPDU(const unsigned char * inTextSender, const typeMessageClass inMessageClass, const unsigned char * inMessageText) { // добавить СМС в очередь на отправку
if (this->_firstMessage) { // уже есть какие то сообщения - ищем последнее
sendItemPDU * fM = this->_firstMessage; // переменной присваиваем указатель на первое сообщение
while (fM->_nextMessage) { // пока у сообщения есть следующее
fM = fM->_nextMessage; // переменной присваиваем указатель на это следующее
}
fM->_nextMessage = new sendItemPDU(inTextSender,
inMessageClass, inMessageText, (const unsigned char *)&this->_PDUSCA[0]); // выделяем память под объект сообщение и указываем указатель в поле следующего
if (fM->_nextMessage) { // память удачно выделилась
++this->_countMessageQueue; // увеличиваем счетчик сообщений
}
} else { // это первое сообщение
this->_firstMessage = new sendItemPDU(inTextSender,
inMessageClass, inMessageText, (const unsigned char *)&this->_PDUSCA[0]); // выделяем память под объект сообщение и указываем указатель в поле следующего
if (this->_firstMessage) { // память удачно выделилась
++this->_countMessageQueue; // увеличиваем счетчик сообщений
}
}
}
bool existSMStoSend(void) { // есть ли что отправлять
// здесь необходимо тщательно подсчитать отправляемые части
// и если уже отправляли сообщение нулевое из списка - удалить его
// и только потом выдавать данные в отправку
if (this->_countMessageQueue) {
if ((this->_firstMessage) && (this->_firstMessage->_countSendParts) && (this->_firstMessage->_countSendParts >= this->_firstMessage->_countPartsAll)) {
// удаляем отправленное сообщение
sendItemPDU * nM = this->_firstMessage->_nextMessage;
this->_firstMessage->deleteTextMessage();
delete this->_firstMessage;
this->_firstMessage = nM;
--this->_countMessageQueue;
}
}
return (this->_countMessageQueue > 0);
}
unsigned char returnCountBytesMsg(void) { // возвращает число байт части сообщения
return this->_firstMessage->returnCountBytesMsgRaw();
}
unsigned char * returnTextMsg(void) { // возвращает часть сообщения на отправку
return this->_firstMessage->returnTextMsgRaw();
}
};
union t_TP_MTIandCo {
struct {
unsigned TP_MTI:2;
unsigned TP_MMS:1;
unsigned res1:1;
unsigned res2:1;
unsigned TP_SRI:1;
unsigned TP_UDHI:1;
unsigned TP_RP:1;
};
unsigned char TP_MTI_Value;
};
class itemSMSbyPDU { // класс для одного сообщения
private:
unsigned char _lenBodyFromHeder; // длина сообщения из заголовка СМС
unsigned short _lenRawMessage; // длина всего сообщаения, принятого от модема
bool _isConverted; // флаг, что сообщение уже преобразовано в UTF-8
unsigned char * _textMessage; // сам текст сообщения
unsigned char * _phoneSender[lenSender]; // отправитель
unsigned char * _centrSMS[lenSender]; // номер смс центра
unsigned char * _workPos; // текущий указатель в тексте для обработки сообщения
t_TP_MTIandCo _TPmessage; // структура конфигурации сообщения
unsigned char _typeTP_OA; // тип адреса отправителя
unsigned char _getByteByChar(const unsigned char * inPos); // преобразования двух текстовых байт в число
unsigned char * _convertCentrSMS(const unsigned char * inPos); // получение номера СМС центра
unsigned char _TP_DCS; // схема кодирования данных
unsigned char _TP_UDL; // длина пользовательских данных
unsigned char _UDHL; // длина заголовка UDH в пользовательских данных UD
unsigned char _IEI; // IEI Для отправки длинных сообщений всегда равен либо 0х00, либо 0х08. Если 0х00 – то IED1 занимает 1 байт, если 0х08 – то IED1 занимает 2 байта.
unsigned char _IEDL; // IEDL Длина данных информационного элемента – 3 или 4 байта, в зависимости от длины поля IED1
void _convertPhoneToString(const unsigned char * inStr, const unsigned char lenBytes, unsigned char * outStr); // коныертация строки из дурацкого формата в строку
void _convert7bitToString(const unsigned char * inStr, const unsigned char lenBytes, unsigned char * outStr, unsigned char inOffset, bool firstPart); // коныертация строки 7и битного формата
void _convertUCS2toString(const unsigned char * inStr, const unsigned char lenBytes, unsigned char * outStr); // коныертация строки UCS формата
void _startConvertPDUmessage(void); // основная функция преобразования PDU формата
protected:
public:
unsigned short _IED1; // IED1 Номер-ссылка. Должен быть одинаков для всех частей сообщения
unsigned char _IED2; // IED2 Количество частей в сообщении
unsigned char _IED3; // IED3 Порядковый номер части сообщения
itemSMSbyPDU * _nextMessage; // указатель на следующее сообщение в памяти
itemSMSbyPDU(const unsigned char * inRawMessage = nullptr, const unsigned short lenRawMessage = 0,
const unsigned char lenBodyFromHeder = 0); // конструктор создателя сообщения
void deleteTextMessage(void){ // освобождение памяти сообщения
delete this->_textMessage; // освободили память
this->_textMessage = nullptr; // обнулили указатель
}
unsigned char * getSMScentr(void) {
if (!this->_isConverted) this->_startConvertPDUmessage();
return (unsigned char *)&this->_centrSMS[0];
}
unsigned char * getSender(void) {
if (!this->_isConverted) this->_startConvertPDUmessage();
return (unsigned char *)&this->_phoneSender[0];
}
unsigned char * getText(void) {
if (!this->_isConverted) this->_startConvertPDUmessage();
return (unsigned char *)&this->_textMessage[0];
}
bool isMulti(void) {
if (!this->_isConverted) this->_startConvertPDUmessage();
return (this->_TPmessage.TP_UDHI != 0);
}
};
class listSMSbyPDU { // класс для всего списка сообщений
private:
unsigned char _countMessages; // количество сообщений в памяти устройства (МК)
itemSMSbyPDU * _firstMessage; // указатель на первое сообщение в памяти
itemSMSbyPDU * getRawMessByNum(const unsigned char inNum) { // возвращает указатель на сообщение по его номеру // от нуля
if ((this->_countMessages == 0) || (inNum >= this->_countMessages)) return nullptr; // если сообщений нет ил не тот номер - выходим
itemSMSbyPDU * cM = this->_firstMessage; // текущее сообщение
unsigned char counterI = 0; // счетчик
while (inNum > counterI) { // добираемся до нужного сообщения
if (cM->_nextMessage) { // если следующее сообщение существует
cM = cM->_nextMessage; // присваиваем его текущему
} else { // если нет следующего
break; // выходим
}
++counterI; // увеличиваем счетчик
}
return cM;
}
protected:
public:
listSMSbyPDU(void) { // конструктор класса
this->_countMessages = 0; // обнулили количоство сообщенией
this->_firstMessage = nullptr; // обнулили указатель на сообщения
}
unsigned char getCountMessages(void) { // возращает количество сообщений
return this->_countMessages;
}
unsigned char * getSender(const unsigned char numMessage) { // возвращает имя/номер отправителя
return this->getRawMessByNum(numMessage)->getSender();
}
unsigned char * getText(const unsigned char numMessage) { // возвращает текст сообщения
return this->getRawMessByNum(numMessage)->getText();
}
void addRawMessageWithLenBody(const unsigned char * inText, const unsigned char inLenBodyHeader) { // добавление нового сообщения с длиной, полученной из заголовка SIM800
unsigned short lenText = strlen((const char *)inText); // получаем длину сообщения
if (this->_firstMessage) { // уже есть какие то сообщения - ищем последнее
itemSMSbyPDU * fM = this->_firstMessage; // переменной присваиваем указатель на первое сообщение
while (fM->_nextMessage) { // пока у сообщения есть следующее
fM = fM->_nextMessage; // переменной присваиваем указатель на это следующее
}
fM->_nextMessage = new itemSMSbyPDU(inText, lenText, inLenBodyHeader); // выделяем память под объект сообщение и указываем указатель в поле следующего
if (fM->_nextMessage) { // память удачно выделилась
++this->_countMessages; // увеличиваем счетчик сообщений
}
} else { // это первое сообщение
this->_firstMessage = new itemSMSbyPDU(inText, lenText, inLenBodyHeader); // выделяем память под объект сообщение
if (this->_firstMessage) { // память удачно выделилась
++this->_countMessages; // увеличиваем счетчик сообщений
}
}
}
void deleteMessage(const unsigned char numMessage) { // удаление сообщения с нужным номером // нумерация с нуля
if ((this->_countMessages == 0) || (numMessage >= this->_countMessages)) return; // если сообщений нет ил не тот номер - выходим
if (numMessage == 0) { // удаляется первое сообщение
itemSMSbyPDU * dM = this->_firstMessage->_nextMessage; // сохраним указатель на второе сообщение
this->_firstMessage->deleteTextMessage(); // удаляем тескт первого сообщения
delete this->_firstMessage; // освобождаем память первого сообщения
this->_firstMessage = dM; // второе сообщение становиться первым
--this->_countMessages; // уменьшаем количество сообщений
} else { // ищем сообщение для удаления
unsigned char posDel = 1; // счетчик сообщений
itemSMSbyPDU * cM = this->_firstMessage; // текущее сообщение в next которого необходимо прописать следующее за удаляемым
itemSMSbyPDU * dM = cM->_nextMessage; // удаляемое сообщение // оно же следующее после текущего
while (numMessage > posDel) { // пока удаляемое сообщение больше счетчика -> переходим на следующее сообщение
if (dM->_nextMessage) { // если есть следующее сообщение
cM = dM; // текщее сообщение берем удаляемое
dM = dM->_nextMessage; // следующее сообщение ставим текущим
++posDel; // увеличиваем счетчик
} else { // нет следующего сообщения
break; // выходим из цикла
}
}
cM->_nextMessage = dM->_nextMessage; // в указатель NExt текущего сообщения прописали указаталь на следующее удаляемого сообщения
dM->deleteTextMessage(); // удаляем текст удаляемого сообщения
delete dM; // усвобождаем память удаляемого сообщения
--this->_countMessages; // уменьшаем количество сообщений
}
}
unsigned char * getRawCentrSMSmessage(const unsigned char numMessage) { // вытаскиваем номер центра сообщения из СМС
return this->getRawMessByNum(numMessage)->getSMScentr();
}
bool getRawIsMulti(const unsigned char numMessage) { // вытаскиваем что многоблочное сообщение
return this->getRawMessByNum(numMessage)->isMulti();
}
unsigned short getRawIDlinkMessage(const unsigned char numMessage) { // номер-ссылка многоблочного сообщения
return this->getRawMessByNum(numMessage)->_IED1;
}
unsigned char getRawCountPartsMessage(const unsigned char numMessage) { // количество частей многоблочного сообщения
return this->getRawMessByNum(numMessage)->_IED2;
}
unsigned char getRawNumPartMessage(const unsigned char numMessage) { // номер части многоблочного сообщения
return this->getRawMessByNum(numMessage)->_IED3;
}
bool isFullMessageGet(const unsigned char numMessage) { // полностью ли получено много блочное сообщение?
if (this->getRawIsMulti(numMessage)) { // проверяем что смс многоблочная
unsigned short cIL = this->getRawIDlinkMessage(numMessage); // получаем номер ссылку сообщения
unsigned char jC = 0; // количество подсчитанных сообщений с совпадающем номером
for (unsigned char i = 0; i < this->_countMessages; ++i) { // цикл по всем сообщениям
if ((this->getRawIsMulti(i)) && (this->getRawIDlinkMessage(i) == cIL)) ++jC; // считаем количество многоблочных сообщений с нужным номером ссылкой
}
if (jC == this->getRawCountPartsMessage(numMessage)) return true; // если число полученных частей равно число необходимых частей - успешно выходим
}
return false;
}
};
#endif /* PDUMSG_H_ */
Спойлер
/*
* pdumsg.cpp
*
* Created on: Feb 21, 2025
* Author: andycat2013@yandex.ru
*
* модуль работы дла работы с SMS сообщениями в PDU формате
* тестировался на STM32F411CEU6 STM32CubeIDE
*
*/
#include <string.h>
#include "pdumsg.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include "f4base.h"
listSMSbyPDU SIM800listPDU; // объект класса всех сообщений устройства
sendListPDU SIM800sendPDU; // объект класса всех отправляемых сообщений
// https://habr.com/ru/companies/yandex/articles/466183/
inline unsigned char calcBytesInCharUTF8(const unsigned char * inChar) {
if ((*inChar) <= 0x7F) {
return 1;
} else if (((*inChar) >= 0xC2) && ((*inChar) <= 0xDF) && (*(inChar + 1) >= 0x80) && (*(inChar + 1) <= 0xBF)) {
return 2;
} else if (((*inChar) >= 0xE1) && ((*inChar) <= 0xEC) && (*(inChar + 1) >= 0x80) && (*(inChar + 1) <= 0xBF) && (*(inChar + 2) >= 0x80) && (*(inChar + 2) <= 0xBF)) {
return 3;
} else if (((*inChar) >= 0xEE) && ((*inChar) <= 0xEF) && (*(inChar + 1) >= 0x80) && (*(inChar + 1) <= 0xBF) && (*(inChar + 2) >= 0x80) && (*(inChar + 2) <= 0xBF)) {
return 3;
} else if (((*inChar) == 0xE0) && (*(inChar + 1) >= 0xA0) && (*(inChar + 1) <= 0xBF) && (*(inChar + 2) >= 0x80) && (*(inChar + 2) <= 0xBF)) {
return 3;
} else if (((*inChar) == 0xED) && (*(inChar + 1) >= 0xA0) && (*(inChar + 1) <= 0x9F) && (*(inChar + 2) >= 0x80) && (*(inChar + 2) <= 0xBF)) {
return 3;
} else if (((*inChar) >= 0xF1) && ((*inChar) <= 0xF3) && (*(inChar + 1) >= 0x80) && (*(inChar + 1) <= 0xBF) && (*(inChar + 2) >= 0x80) && (*(inChar + 2) <= 0xBF) && (*(inChar + 3) >= 0x80) && (*(inChar + 3) <= 0xBF)) {
return 4;
} else if (((*inChar) == 0xF0) && (*(inChar + 1) >= 0x90) && (*(inChar + 1) <= 0xBF) && (*(inChar + 2) >= 0x80) && (*(inChar + 2) <= 0xBF) && (*(inChar + 3) >= 0x80) && (*(inChar + 3) <= 0xBF)) {
return 4;
} else if (((*inChar) == 0xF4) && (*(inChar + 1) >= 0x80) && (*(inChar + 1) <= 0x8F) && (*(inChar + 2) >= 0x80) && (*(inChar + 2) <= 0xBF) && (*(inChar + 3) >= 0x80) && (*(inChar + 3) <= 0xBF)) {
return 4;
}
return 0;
}
unsigned short isValidUTF8len(unsigned char * inStr, const unsigned short inLen) { // проверяет строку на коректность, выдает длину в символах, исправляет кривые байты на проблелы
unsigned short outCountChars = 0;
unsigned short posGlobalInStr = 0;
while (posGlobalInStr < inLen) {
unsigned char countBytesChar = calcBytesInCharUTF8(inStr + posGlobalInStr);
if (countBytesChar >= 2) { // normal char > 2 bytes
posGlobalInStr += countBytesChar - 1;
} else if (countBytesChar == 0) { // bad byte
*(inStr + posGlobalInStr) = ' ';
}
++outCountChars;
++posGlobalInStr;
}
return outCountChars;
}
unsigned char * sendItemPDU::_addByteToStringAs2char(const unsigned char * inS, const unsigned char inByte) {
unsigned char * pStr = (unsigned char *)inS;
char sN[3] = {'\0', '\0', '\0'};
sprintf((char*)&sN[0], "%02X", inByte);
strcat((char *)pStr, (const char *)&sN[0]);
return pStr + 2;
}
unsigned char * sendItemPDU::_addSCA(const unsigned char * inS) {
unsigned char tempPhone[lenSender];
unsigned char * tempPointer = &tempPhone[0];
memset(tempPointer, '\0', lenSender);
unsigned char * pStr = (unsigned char *)inS;
unsigned char lenPhone = strlen((const char *)this->_SCAnumber);
unsigned char outLenField = 1;
unsigned char outFormatField = 0x91;
if (lenPhone) {
unsigned char posPhone = 0;
while (posPhone < lenPhone) {
if ((lenPhone - posPhone) == 1) { // last char // нечетная длина телефона
*(tempPointer++) = 'F';
*(tempPointer++) = *(this->_SCAnumber + posPhone);
++outLenField;
++posPhone;
} else {
*(tempPointer++) = *(this->_SCAnumber + posPhone + 1);
*(tempPointer++) = *(this->_SCAnumber + posPhone);
++outLenField;
posPhone += 2;
}
}
}
pStr = this->_addByteToStringAs2char(pStr, outLenField);
pStr = this->_addByteToStringAs2char(pStr, outFormatField);
strcat((char*)pStr, (const char *)&tempPhone[0]);
pStr += strlen((const char *)&tempPhone[0]);
return pStr;
}
unsigned char * sendItemPDU::_addDA(const unsigned char * inS) {
unsigned char tempPhone[lenSender];
unsigned char * tempPointer = &tempPhone[0];
memset(tempPointer, '\0', lenSender);
unsigned char * pStr = (unsigned char *)inS;
unsigned char lenPhone = strlen((const char *)_textSender);
unsigned char outLenField = lenPhone;
unsigned char outFormatField = 0x91;
if (lenPhone) {
// international format
unsigned char posPhone = 0;
while (posPhone < lenPhone) {
if ((lenPhone - posPhone) == 1) { // last char // нечетная длина телефона
*(tempPointer++) = 'F';
*(tempPointer++) = *(this->_textSender + posPhone);
++posPhone;
} else {
*(tempPointer++) = *(this->_textSender + posPhone + 1);
*(tempPointer++) = *(this->_textSender + posPhone);
posPhone += 2;
}
}
}
pStr = this->_addByteToStringAs2char(pStr, outLenField);
pStr = this->_addByteToStringAs2char(pStr, outFormatField);
strcat((char*)pStr, (const char *)&tempPhone[0]);
pStr += strlen((const char *)&tempPhone[0]);
this->_lenBytesCalc = (pStr - inS) / 2;
return pStr;
}
unsigned char * sendItemPDU::_convert7bitsToStringOut(const unsigned char * inPosition, const unsigned char * inStringASCII) {
unsigned char * pStr = (unsigned char *)inPosition;
unsigned char posInStr = 0;
unsigned char offsets = 0;
while ((*(inStringASCII + posInStr)) && (posInStr < 160)) {
unsigned char lowMask = 0;
unsigned char i = 0;
do {
lowMask |= 0x01 << i;
++i;
} while (i < (7-offsets));
unsigned char inputByte = ((*(inStringASCII + posInStr)) >> offsets) & lowMask;
unsigned char highBits = ((*(inStringASCII + posInStr + 1)) << (7 - offsets));
unsigned char outByte = highBits | inputByte;
pStr = this->_addByteToStringAs2char(pStr, outByte);
if ((++offsets) == 7) {
++posInStr;
offsets = 0;
}
++posInStr;
}
this->_lenBytesCalc = (pStr - inPosition) / 2;
return pStr;
}
// https://hardisoft.ru/soft/samodelkin-soft/otpravka-sms-soobshhenij-v-formate-pdu-teoriya-s-primerami-na-c-chast-1/?ysclid=m6v4ff2wd3481016863
void sendItemPDU::_convert7bitsToOnePartPDU(void) {
unsigned char tS[maxLenMultiMessage];
unsigned char * ptS = &tS[0];
memset(ptS, '\0', maxLenMultiMessage);
ptS = this->_addSCA(ptS); // TP-SCA
unsigned char lenBytesToMsg2charOut = 0; // len calculate without TP-SCA
ptS = this->_addByteToStringAs2char(ptS, 0x11); // PDU-TYPE=0x11 / TP-VPF=b10-> 1 byte TP-VP — TP-Validity-Period / TP-MTI=b1 -> output message / no UDHI -> one part
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, 0x00); // TP-MR
++lenBytesToMsg2charOut;
ptS = this->_addDA(ptS); // TP-DA
lenBytesToMsg2charOut += this->_lenBytesCalc;
ptS = this->_addByteToStringAs2char(ptS, 0x00); // TP-PID
++lenBytesToMsg2charOut;
if (this->_classMSG == classMessageFlash) ptS = this->_addByteToStringAs2char(ptS, 0x10); // TP-DSC / 7bit coding -> 0x10
else ptS = this->_addByteToStringAs2char(ptS, 0x00); // TP-DSC / 7bit coding -> 0x00
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, 0x0B); // TP-VP / by hours ?
++lenBytesToMsg2charOut;
unsigned short lenUDL = this->_lenText;
if (lenUDL > 160) lenUDL = 160; // max 160 chars in one part
ptS = this->_addByteToStringAs2char(ptS, (unsigned char)lenUDL); // TP-UDL / count chars in message
++lenBytesToMsg2charOut;
_convert7bitsToStringOut(ptS, this->_textMessage);
lenBytesToMsg2charOut += this->_lenBytesCalc;
#ifdef SHOW_LOG_PDU
printf("\r\n%d %d %s %d\r\n", lenBytesToMsg2charOut, lenUDL, (char*)&tS[0], this->_lenText);
#endif
unsigned short lenOutStr = strlen((char*)&tS[0]);
this->_addPartInMsg((unsigned char)(lenBytesToMsg2charOut & 0x00FF), (const unsigned char*)&tS[0], lenOutStr);
}
unsigned char * sendItemPDU::_convertUCStoStringOut(const unsigned char * inPosition, const unsigned char * inUTF8, const unsigned char maxOutChars, unsigned char ** outPtr) { // только русская разборка UTF-8 !
unsigned char * pStr = (unsigned char *)inPosition;
unsigned char inPos = 0;
unsigned char countOutChars = 0;
while (*(inUTF8 + inPos)) {
unsigned char iB = *(inUTF8 + inPos);
unsigned char iN = *(inUTF8 + inPos + 1);
if (iB == 0xD0) {
if ((iN >= 0x90) && (iN <= 0xBF)) {
pStr = this->_addByteToStringAs2char(pStr, 0x04);
pStr = this->_addByteToStringAs2char(pStr, iN - (0x90 - 0x10));
++inPos;
} else if (iN == 0x81) {
pStr = this->_addByteToStringAs2char(pStr, 0x04);
pStr = this->_addByteToStringAs2char(pStr, 0x01);
++inPos;
} else {
pStr = this->_addByteToStringAs2char(pStr, iB);
pStr = this->_addByteToStringAs2char(pStr, iN);
++inPos;
}
} else if (iB == 0xD1) {
if ((iN >= 0x80) && (iN <= 0x8F)) {
pStr = this->_addByteToStringAs2char(pStr, 0x04);
pStr = this->_addByteToStringAs2char(pStr, iN - (0x80 - 0x40));
++inPos;
} else if (iN == 0x91) {
pStr = this->_addByteToStringAs2char(pStr, 0x04);
pStr = this->_addByteToStringAs2char(pStr, 0x51);
++inPos;
} else {
pStr = this->_addByteToStringAs2char(pStr, iB);
pStr = this->_addByteToStringAs2char(pStr, iN);
++inPos;
}
} else if (iB) {
pStr = this->_addByteToStringAs2char(pStr, 0x00);
pStr = this->_addByteToStringAs2char(pStr, iB);
}
++inPos;
if ((++countOutChars) >= maxOutChars) break;
}
if (outPtr) { // если в функцию давали адрес на указатель на строку
*outPtr = (unsigned char *)inUTF8 + inPos; // возвращаем указатель на место где следующий символ
}
this->_lenBytesCalc = (pStr - inPosition) / 2;
return pStr;
}
void sendItemPDU::_convertUCS2toOnePartPDU(unsigned short inUTF8chars) {
unsigned char tS[maxLenMultiMessage];
unsigned char * ptS = &tS[0];
memset(ptS, '\0', maxLenMultiMessage);
ptS = this->_addSCA(ptS); // TP-SCA
unsigned char lenBytesToMsg2charOut = 0; // len calculate without TP-SCA
ptS = this->_addByteToStringAs2char(ptS, 0x11); // PDU-TYPE=0x11 / TP-VPF=b10-> 1 byte TP-VP — TP-Validity-Period / TP-MTI=b1 -> output message / no UDHI -> one part
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, 0x00); // TP-MR
++lenBytesToMsg2charOut;
ptS = this->_addDA(ptS); // TP-DA
lenBytesToMsg2charOut += this->_lenBytesCalc;
ptS = this->_addByteToStringAs2char(ptS, 0x00); // TP-PID
++lenBytesToMsg2charOut;
if (this->_classMSG == classMessageFlash) ptS = this->_addByteToStringAs2char(ptS, 0x18); // TP-DSC / UCS coding -> 0x18
else ptS = this->_addByteToStringAs2char(ptS, 0x08); // TP-DSC / UCS coding -> 0x08
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, 0x0B); // TP-VP / by hours ?
++lenBytesToMsg2charOut;
unsigned short lenUDL = inUTF8chars * 2;
ptS = this->_addByteToStringAs2char(ptS, (unsigned char)lenUDL); // TP-UDL / count bytes in message
++lenBytesToMsg2charOut;
_convertUCStoStringOut(ptS, this->_textMessage, inUTF8chars);
lenBytesToMsg2charOut += this->_lenBytesCalc;
unsigned short lenOutStr = strlen((char*)&tS[0]);
this->_addPartInMsg((unsigned char)(lenBytesToMsg2charOut & 0x00FF), (const unsigned char*)&tS[0], lenOutStr);
#ifdef SHOW_LOG_PDU
printf("\r\n%d %d %s %d\r\n", lenBytesToMsg2charOut, lenUDL, (char*)&tS[0], this->_lenText);
#endif
}
void sendItemPDU::_convertUCS2toMultiPartsPDU(unsigned short inUTF8chars) {
constexpr unsigned short charsInOnePart = 67;
unsigned short countWorkParts = inUTF8chars / charsInOnePart;
unsigned short countRemainsBytes = inUTF8chars % charsInOnePart;
if (countRemainsBytes) ++countWorkParts;
unsigned char tS[maxLenMultiMessage];
unsigned char numberMSG = rand() + millis();
unsigned char * getPtr = this->_textMessage;
unsigned char ** ptrGet = &getPtr;
for (unsigned short i = 0; i < countWorkParts; ++i) {
// - общее для всех частей - заголовок
unsigned char * ptS = &tS[0];
memset(ptS, '\0', maxLenMultiMessage);
ptS = this->_addSCA(ptS); // TP-SCA
unsigned char lenBytesToMsg2charOut = 0; // len calculate without TP-SCA
ptS = this->_addByteToStringAs2char(ptS, 0x51); // PDU-TYPE=0x11 / TP-VPF=b10-> 1 byte TP-VP — TP-Validity-Period / TP-MTI=b1 -> output message / UDHI -> multi part
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, 0x00); // TP-MR
++lenBytesToMsg2charOut;
ptS = this->_addDA(ptS); // TP-DA
lenBytesToMsg2charOut += this->_lenBytesCalc;
ptS = this->_addByteToStringAs2char(ptS, 0x00); // TP-PID
++lenBytesToMsg2charOut;
if (this->_classMSG == classMessageFlash) ptS = this->_addByteToStringAs2char(ptS, 0x18); // TP-DSC / UCS coding -> 0x18
else ptS = this->_addByteToStringAs2char(ptS, 0x08); // TP-DSC / UCS coding -> 0x08
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, 0x0B); // TP-VP / by hours ?
++lenBytesToMsg2charOut;
// - тут уже идут различия в частях
unsigned short lenUDL; // = inUTF8chars * 2;
if ((countRemainsBytes) && ((i+1) == (countWorkParts))) { // last part
lenUDL = countRemainsBytes * 2 + 6; // 05 UDHL + 1bUDHL
} else {
lenUDL = charsInOnePart * 2 + 6; // 05 UDHL + 1bUDHL
}
ptS = this->_addByteToStringAs2char(ptS, (unsigned char)lenUDL); // TP-UDL / count bytes in message
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, 0x05); // UDHL
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, 0x00); // IEI
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, 0x03); // IEDL
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, numberMSG); // IED1
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, (unsigned char)countWorkParts); // IED2
++lenBytesToMsg2charOut;
ptS = this->_addByteToStringAs2char(ptS, (unsigned char)(i+1)); // IED3
++lenBytesToMsg2charOut;
if ((countRemainsBytes) && (i == (countWorkParts))) { // last part
_convertUCStoStringOut(ptS, getPtr, countRemainsBytes, ptrGet);
} else {
_convertUCStoStringOut(ptS, getPtr, charsInOnePart, ptrGet);
}
lenBytesToMsg2charOut += this->_lenBytesCalc;
unsigned short lenOutStr = strlen((char*)&tS[0]);
#ifdef SHOW_LOG_PDU
printf("\r\n%d %d %d %s %d\r\n", i, lenBytesToMsg2charOut, lenUDL, (char*)&tS[0], this->_lenText);
#endif
this->_addPartInMsg((unsigned char)(lenBytesToMsg2charOut & 0x00FF), (const unsigned char*)&tS[0], lenOutStr);
}
}
void sendItemPDU::_convertPDUtoParts(void) {
unsigned short i = 0; // индекс по символам
while (*(this->_textMessage + i)) { // цикл по входящей строке
if ((*(this->_textMessage + i) < 0x20) || (*(this->_textMessage + i) >= 0x7F)) { // not ASCII
this->_textFormat = messageUCS2;
break;
}
++i;
}
if (this->_textFormat == messageUCS2) {
unsigned short countUTF8chars = isValidUTF8len(this->_textMessage, this->_lenText);
if (countUTF8chars <= 70) {
this->_convertUCS2toOnePartPDU(countUTF8chars);
} else {
this->_convertUCS2toMultiPartsPDU(countUTF8chars);
}
} else {
this->_convert7bitsToOnePartPDU();
}
this->_isConverted = true;
}
unsigned char sendItemPDU::returnCountBytesMsgRaw(void) {
if (!this->_isConverted) this->_convertPDUtoParts();
if ((this->_countPartsAll) && (this->_firstPart)) {
unsigned char outPart = 0;
itemOnePart * nP = this->_firstPart;
while (this->_countSendParts > outPart) {
if (nP->_nextPart) nP = nP->_nextPart;
++outPart;
}
return nP->_countBytesToOut;
}
return 0;
}
unsigned char * sendItemPDU::returnTextMsgRaw(void) {
if (!this->_isConverted) this->_convertPDUtoParts();
if ((this->_countPartsAll) && (this->_firstPart)) {
unsigned char outPart = 0;
itemOnePart * nP = this->_firstPart;
while (this->_countSendParts > outPart) {
if (nP->_nextPart) nP = nP->_nextPart;
++outPart;
}
++this->_countSendParts;
return nP->_textPart;
}
return nullptr;
}
sendItemPDU::sendItemPDU(const unsigned char * inTextSender,
const typeMessageClass inMessageClass, const unsigned char * inMessageText,
const unsigned char * inSCAnumber) { // конструктор создателя сообщения
if (inTextSender) strcpy((char*)&this->_textSender[0], (const char *)inTextSender); else memset((unsigned char *)&this->_textSender[0], '\0', lenSender);
this->_firstPart = nullptr;
this->_countSendParts = 0;
this->_countPartsAll = 0;
this->_classMSG = inMessageClass;
this->_lenText = strlen((char*)inMessageText);
this->_textMessage = new unsigned char[this->_lenText + 1]; // выделили память под текcт сообщения // + один нуль символ
if (this->_textMessage) { // если память нормально выделилась
memset((unsigned char *) this->_textMessage, '\0', this->_lenText + 1); // очистили текст сообщения // + один нуль символ
strcpy((char *)this->_textMessage, (const char *)inMessageText); // копируем строку к себе в объект
}
if (inSCAnumber) strcpy((char*)&this->_SCAnumber[0], (const char *)inSCAnumber); else memset((unsigned char *)&this->_SCAnumber[0], '\0', lenSender);
this->_nextMessage = nullptr;
this->_isConverted = false;
this->_textFormat = messageText7bit;
}
unsigned char itemSMSbyPDU::_getByteByChar(const unsigned char * inPos) { // преобразования двух текстовых байт в число
unsigned char outByte = 0;
unsigned char c1 = *inPos;
if (isdigit(c1)) outByte = c1 - '0'; else outByte = c1 - 55;
outByte *= 16;
c1 = *(inPos + 1);
if (isdigit(c1)) outByte += c1 - '0'; else outByte += c1 - 55;
return outByte;
}
void itemSMSbyPDU::_convertPhoneToString(const unsigned char * inStr, const unsigned char lenBytes, unsigned char * outStr) { // коныертация строки из дурацкого формата в строку
for(unsigned char i = 0; i < lenBytes; ++i) { // цикл по байтам (не по символам!) входящей строки
unsigned char iB = *(inStr + i * 2 + 1); // младшая часть полубайта исходной строки
*(outStr + i * 2) = iB; // заносим в старшую часть полубайта строки назначения
iB = *(inStr + i * 2); // старшая часть полубайта исходной строки
if (iB != 'F') *(outStr + i * 2 + 1) = iB; // заносим в младшую часть полубайта строки назначения
}
}
unsigned char * itemSMSbyPDU::_convertCentrSMS(const unsigned char * inPos) { // получение номера СМС центра
unsigned char * outPos = (unsigned char *)inPos; // выходная позиция указателя в строке
unsigned char lenField = this->_getByteByChar(inPos); // длина строки номера центра сообщения вместе с типом в байтах
unsigned char typeField = this->_getByteByChar(inPos + 2); // тип строки номера центра сообщения вместе с типом 91h - это тип 79...
if (typeField == 0x91) { // основной тип в РФ, остальные не знаю как расшифровывать
unsigned char * wP = (unsigned char *)inPos + 4; // рабочая позиция - начало номера
this->_convertPhoneToString(wP, lenField - 1, (unsigned char *)this->_centrSMS); // преобразуем номер центра сообщений в человеческий вид
}
outPos += (lenField * 2) + 2; // сдвигаем указатель на длину поля центра смс
return outPos;
}
void itemSMSbyPDU::_convert7bitToString(const unsigned char * inStr, const unsigned char lenBytes, unsigned char * outStr, unsigned char inOffset, bool firstPart) { // коныертация строки 7и битного формата
unsigned short posInStr = 0; // позиция/индекс символа из входящей строки
unsigned short countPosInStr = lenBytes * 2; // сколько полубайт надо пробежаться циклом
unsigned char posOutStr = 0; // позиция/индекс символа в выходную строку
unsigned char prevDataByte = 0; // предыдущий входящий байт
unsigned char changeOutOffset = 0; // байт сдвига выходного буфера
#ifdef SHOW_LOG_PDU
printf("len msg %d bytes body work\r\n", lenBytes);
#endif
if (inOffset) { // если первый байт сдвигается
*outStr = this->_getByteByChar(inStr) >> inOffset; // кладем в выходной буфер со сдвигом
posInStr += 2; // сразу сдвинем указатель в строке на следующий байт
changeOutOffset = 1; // будем на 1 символ больше класть в выходной буфер
if (!firstPart) countPosInStr += 2; // увеличиваем/сдвигаем предел обработки входящих байтов
#ifdef SHOW_LOG_PDU
printf("off %d\r\n", inOffset);
#endif
}
while (posInStr < countPosInStr) { // цикл по всем байтам // целиковым, т е подубайт в два раза больше
unsigned char inFullByte = this->_getByteByChar(inStr+posInStr); // читаем целиковый байт
posInStr += 2; // сразу сдвинем указатель в строке на следующий байт
unsigned char idxCurrByte = posOutStr % 8; // индекс сдвига входящего байта 0...7
if (idxCurrByte) {
unsigned char lowBits = prevDataByte >> (8 - idxCurrByte);
*(outStr+posOutStr+changeOutOffset) = ((inFullByte << idxCurrByte) & 0x7F) | lowBits;
++posOutStr;
if (idxCurrByte == 7) {
*(outStr+posOutStr+changeOutOffset) = inFullByte & 0x7F;
++posOutStr;
}
} else {
*(outStr+posOutStr+changeOutOffset) = inFullByte & 0x7F;
++posOutStr;
}
prevDataByte = inFullByte; // сохраним для след байта данных
*(outStr+posOutStr+changeOutOffset) = '\0'; // сразу след выходным байтом прописываем конец стоки, т к выводить данные будем в тот же буфер что исходное сообщение
}
if ((inOffset) && (firstPart)) { // если первый байт сдвигается
*(outStr+posOutStr+changeOutOffset) = (prevDataByte >> inOffset) & 0x7F; // кладем в выходной буфер со сдвигом последний обработанный байт
++posOutStr;
*(outStr+posOutStr+changeOutOffset) = '\0'; // сразу след выходным байтом прописываем конец стоки, т к выводить данные будем в тот же буфер что исходное сообщение
#ifdef SHOW_LOG_PDU
printf("last c %02X\r\n", prevDataByte);
#endif
}
}
void itemSMSbyPDU::_convertUCS2toString(const unsigned char * inStr, const unsigned char lenBytes, unsigned char * outStr) { // коныертация строки UCS формата
#ifdef SHOW_LOG_PDU
printf("len msg %d bytes work UCS2\r\n", lenBytes);
#endif
for (unsigned char i = 0; i < lenBytes; ++i) { // цикл по всем байтам
unsigned char iB = this->_getByteByChar(inStr + i * 2); // читаем байт
// конвертируем UCS2 (Unicode) в UTF-8
if (iB == 0x04) {
unsigned char iN = this->_getByteByChar(inStr + i * 2 + 2); // читаем next байт
if ((iN >= 0x10) && (iN <= 0x3F)) {
*(outStr++) = 0xD0;
*(outStr++) = iN + (0x90 - 0x10);
*outStr = '\0';
++i;
} else if ((iN >= 0x40) && (iN <= 0x4F)) {
*(outStr++) = 0xD1;
*(outStr++) = iN + (0x80 - 0x40);
*outStr = '\0';
++i;
} else if (iN == 0x01) {
*(outStr++) = 0xD0;
*(outStr++) = 0x81;
*outStr = '\0';
++i;
} else if (iN == 0x51) {
*(outStr++) = 0xD1;
*(outStr++) = 0x91;
*outStr = '\0';
++i;
} else if (iB) {
*(outStr++) = iB; // заносим его в выходную строку
*outStr = '\0'; // сразу прописываем нуль терминатор строки
}
} else if (iB) {
*(outStr++) = iB; // заносим его в выходную строку
*outStr = '\0'; // сразу прописываем нуль терминатор строки
}
}
}
void itemSMSbyPDU::_startConvertPDUmessage(void) { // основная функция преобразования PDU формата
this->_workPos = this->_textMessage; // начало работы конвертова - начало строки тела сообщения
if ((this->_lenRawMessage % 2) == 0) { // количество символов должно быть четным
// TP-SCA // если длина сообщения в символах (разделить на 2 будет в байтах) больше длины тела, полученного в заголовке (в байтех), значит в начале - номер центра сообщений
if ((this->_lenRawMessage / 2) > this->_lenBodyFromHeder) this->_workPos = _convertCentrSMS(this->_workPos); // получаем номер центра сообщений
// https://hardisoft.ru/soft/samodelkin-soft/otpravka-sms-soobshhenij-v-formate-pdu-teoriya-s-primerami-na-c-chast-1/?ysclid=m6v4ff2wd3481016863
this->_TPmessage.TP_MTI_Value = this->_getByteByChar(this->_workPos); // считываем параметры сообщения
this->_workPos += 2; // сдвигаем позицию обработки на байт
// адрес отправителя TP-OA
unsigned char lenTP_OA = this->_getByteByChar(this->_workPos); // длина строки отправителя // полубайт
// данное число не может быть по определению нечетным
// т к все равно преобразуем в байты, т е символы парные
// если длина нечетная, значит в телефоне нечетное количество цифр или нечетное количество символов
if ((lenTP_OA % 2) != 0) ++lenTP_OA; // в любом случае делаем четное количество
this->_workPos += 2; // указатель сдвигаем на два символа вправо = 1 байт
this->_typeTP_OA = this->_getByteByChar(this->_workPos); // формат строки отправителя
this->_workPos += 2; // указатель сдвигаем на два символа вправо = 1 байт
switch (this->_typeTP_OA) {
case 0x91: { // международный формат номера 7...
this->_convertPhoneToString(this->_workPos, lenTP_OA / 2, (unsigned char *)this->_phoneSender); // преобразуем телефон отправителя в человеческий формат
break;
}
default:{ // не буду расшифровывать все форматы отправителя, все что не международный формат
// будем считать что отправитель это текстовый формат в 7и битной кодировке
// https://hardisoft.ru/soft/samodelkin-soft/poluchenie-i-dekodirovanie-sms-soobshhenij-v-formate-pdu/?ysclid=m7ipu29rxq89414721
this->_convert7bitToString(this->_workPos, lenTP_OA / 2, (unsigned char *)this->_phoneSender, 0, true); // преобразуем отправителя в человеческий формат
}
}
this->_workPos += lenTP_OA; // указатель сдвигаем на длину поля отправителя
// TP-PID (Protocol identifier) – Идентификатор протокола. // не используем - пропускаем
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
// TP-DCS (Data coding scheme) – Схема кодирования данных.
// 00 — 7-битная сжатая кодировка, 08 — UCS2, 10 и 18 — то же, только сообщение класса 0, т.е. Flash.
this->_TP_DCS = this->_getByteByChar(this->_workPos);
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
// TP-SCTS (The service centre time stamp) – Штамп времени сервисного центра. Не используем.
this->_workPos +=14; // указатель сдвигаем на 7 байт
// TP-UDL (User Data Length) — длина пользовательских данных, включая User Data header, если он есть. Если SMS закодирована 7-битной кодировкой, то данное поле указывает количество символов в сообщении. Если кодировка UCS2 – то поле указывает количество байт в сообщении.
this->_TP_UDL = this->_getByteByChar(this->_workPos);
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
#ifdef SHOW_LOG_PDU
printf("TP-MTI %d\r\n", this->_TPmessage.TP_MTI);
printf("TP-MMS %d\r\n", this->_TPmessage.TP_MMS);
printf("TP-SRI %d\r\n", this->_TPmessage.TP_SRI);
printf("TP-UDHI %d\r\n", this->_TPmessage.TP_UDHI);
printf("TP-RP %d\r\n", this->_TPmessage.TP_RP);
printf("Centr %s\r\n", (char *)this->_centrSMS);
printf("Sender %s\r\n", (char *)this->_phoneSender);
printf("TP-DCS %02x\r\n", this->_TP_DCS);
printf("TP-UDL %d\r\n", this->_TP_UDL);
#endif
// TP-UD (User Data) – Поле пользовательских данных. Здесь находится сам текст сообщения. В случае, если SMS длинная, т.е. разбита на несколько сообщений, данное поле начинается с заголовка TP-UDH. На наличие этого заголовка указывает бит TP-UDHI поля TP-MTI & Co.
if (this->_TPmessage.TP_UDHI == 0) { // сообщение из одного блока
if ((this->_TP_DCS == 0x00) || (this->_TP_DCS == 0x10)) { // 7и битная кодировка
// необходимо подсчитать число полубайт и байт длины 7и битного сообщения
unsigned short lenBits = 7 * this->_TP_UDL; // число бит в сообщении = число символов * 7
unsigned short celBytes = lenBits / 8; // целое число байтов, занимаемое сообщением
if (lenBits % 8) ++celBytes; // если остается хвост от деления на 8 - прибавляем один байт длины
this->_convert7bitToString(this->_workPos, celBytes, this->_textMessage, 0, true); // преобразуем сообщение из 7и битного формата
} else if ((this->_TP_DCS == 0x08) || (this->_TP_DCS == 0x18)) { // кодировка UCS2
this->_convertUCS2toString(this->_workPos, this->_TP_UDL, this->_textMessage); // преобразуем сообщение из UCS2 формата
}
} else { // многоблочное сообщение
this->_UDHL = this->_getByteByChar(this->_workPos); // длина заголовка UDH в пользовательских данных UD
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
// UDH
// IEI Для отправки длинных сообщений всегда равен либо 0х00, либо 0х08. Если 0х00 – то IED1 занимает 1 байт, если 0х08 – то IED1 занимает 2 байта.
this->_IEI = this->_getByteByChar(this->_workPos);
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
// IEDL Длина данных информационного элемента – 3 или 4 байта, в зависимости от длины поля IED1
this->_IEDL = this->_getByteByChar(this->_workPos);
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
// IED1 Номер-ссылка. Должен быть одинаков для всех частей сообщения
if (this->_IEI == 0x00) {
this->_IED1 = this->_getByteByChar(this->_workPos);
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
} else {
this->_IED1 = this->_getByteByChar(this->_workPos) * 256;
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
this->_IED1 += this->_getByteByChar(this->_workPos);
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
}
// IED2 Количество частей в сообщении
this->_IED2 = this->_getByteByChar(this->_workPos);
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
// IED3 Порядковый номер части сообщения
this->_IED3 = this->_getByteByChar(this->_workPos);
this->_workPos +=2; // указатель сдвигаем на два символа вправо = 1 байт
#ifdef SHOW_LOG_PDU
printf("UDHL %02x\r\n", this->_UDHL);
printf("IEI %02x\r\n", this->_IEI);
printf("IEDL %02x\r\n", this->_IEDL);
printf("IED1 %04x\r\n", this->_IED1);
printf("IED2 %02x\r\n", this->_IED2);
printf("IED3 %02x\r\n", this->_IED3);
#endif
/*
* https://translated.turbopages.org/proxy_u/en-ru.ru.51a06ec6-67bc6fc5-945ef9da-74722d776562/https/stackoverflow.com/questions/11489025/when-i-encode-decode-sms-pdu-gsm-7-bit-user-data-do-i-need-prepend-the-udh-fi
*
* Если используются 7-битные данные и заголовок TP-UD не заканчивается на границе септета,
* то после последнего октета данных информационного элемента вставляются заполняющие биты,
* чтобы для всего заголовка TP-UD было целое число септетов.
* Это делается для того, чтобы сам SM начинался с границы октета, чтобы мобильное устройство,
* работающее на более ранней фазе, могло отобразить сам SM,
* хотя заголовок TP-UD в поле TP-UD может быть непонятен.
*
*
* This is to ensure that the SM itself starts on an octet boundary so that an earlier phase mobile will be capable of displaying the SM itself although the TP-UD Header in the TP-UD field may not be understood
*
* */
if ((this->_TP_DCS == 0x00) || (this->_TP_DCS == 0x10)) { // 7и битная кодировка
// считаем смещение септета
static unsigned short offset7bitFirstChar = 0;
unsigned short lenBytesWithUDH = this->_TP_UDL*7/8; // длина поля UD с заголовком UDH
unsigned short lenBitsWOUDH = (lenBytesWithUDH - this->_UDHL - 1 /*UDHL field*/) * 8; // длина поля с сообщением в битах уже без заголовка
unsigned short count7bitChars = lenBitsWOUDH / 7; // количество целиковых 7и битных символов
if ((this->_IED3 == 1) && (this->_IED2 > 1)) { // считаем для первой части сообщения, для остальных частей он аналогичный // и надейться что чтение номеров СМС идет последовательно
offset7bitFirstChar = lenBitsWOUDH - (count7bitChars * 7); // filler 0-6 бит Наполнитель, который выравнивает text по границе септета
}
// необходимо подсчитать число полубайт и байт длины 7и битного сообщения
unsigned short celBytes = lenBitsWOUDH / 8; // целое число байтов, занимаемое сообщением
if (lenBitsWOUDH % 8) ++celBytes; // если остается хвост от деления на 8 - прибавляем один байт длины
if ((this->_IED3 == 1) && (this->_IED2 > 1)) this->_convert7bitToString(this->_workPos, celBytes, this->_textMessage, offset7bitFirstChar, true); // преобразуем сообщение из 7и битного формата
else this->_convert7bitToString(this->_workPos, celBytes, this->_textMessage, offset7bitFirstChar, false); // преобразуем сообщение из 7и битного формата
} else if ((this->_TP_DCS == 0x08) || (this->_TP_DCS == 0x18)) { // кодировка UCS2
this->_convertUCS2toString(this->_workPos, (this->_TP_UDL - this->_UDHL) - 1 /*UDHL field*/, this->_textMessage); // преобразуем сообщение из UCS2 формата
}
}
}
this->_isConverted = true; // ставим флаг что закончили преобразование
}
itemSMSbyPDU::itemSMSbyPDU(const unsigned char * inRawMessage, const unsigned short lenRawMessage,
const unsigned char lenBodyFromHeder) { // конструктор создателя сообщения
this->_lenBodyFromHeder = lenBodyFromHeder; // сохранили длину тела сообщения из заголовка
this->_lenRawMessage = lenRawMessage; // сохранили длину всего пришедшего сообщения
this->_nextMessage = nullptr; // следующего сообщения нет
this->_isConverted = false; // сообщение "сырое" еще не конвертированное
memset((unsigned char *) &this->_phoneSender[0], '\0', lenSender); // очистили строку отправителя
memset((unsigned char *) &this->_centrSMS[0], '\0', lenSender); // очистили строку смс центра
this->_workPos = nullptr; // обнулим рабочий указатель
this->_TP_DCS = 0; // схема кодирования данных неизвестна
this->_TP_UDL = 0;
this->_UDHL = 0;
this->_IEI = 0;
this->_IEDL = 0;
this->_IED1 = 0;
this->_IED2 = 0;
this->_IED3 = 0;
++this->_lenRawMessage; // строка сообщения на 1 символ нулевой больше
this->_textMessage = new unsigned char[this->_lenRawMessage]; // выделили память под тескт сообщения
if (this->_textMessage) { // если память нормально выделилась
memset((unsigned char *) this->_textMessage, '\0', this->_lenRawMessage); // очистили текст сообщения
strcpy((char *)this->_textMessage, (const char *)inRawMessage); // копируем строку к себе в объект
// в итоговой строке удаляем все, что не относиться к сообщению, т е оставляем только цифры и большую латиницу A...F
unsigned short posS = 0; // индекс в строке
while ((posS < this->_lenRawMessage) && (*(this->_textMessage + posS))) { // цикл по всей строке
if (!((((*(this->_textMessage + posS)) >= '0') && ((*(this->_textMessage + posS)) <= '9')) ||
(((*(this->_textMessage + posS)) >= 'A') && ((*(this->_textMessage + posS)) <= 'F')))) {
*(this->_textMessage + posS) = '\0';
}
++posS; // переходим на след символ
}
this->_lenRawMessage = strlen((const char *)this->_textMessage); // присвоили переменной реальное значение сырого сообщения в байтах
}
}
void addPduMessageWithLenBody(const unsigned char * inText, const unsigned char inLenBodyHeader) { // добавление нового сообщения с длиной, полученной из заголовка SIM800
SIM800listPDU.addRawMessageWithLenBody(inText, inLenBodyHeader);
}
void deletePduMessage(const unsigned char numMessage){ // удаление сообщения с нужным номером // нумерация с нуля
SIM800listPDU.deleteMessage(numMessage);
}
unsigned char countPduMessages(void) { // число сообщений
return SIM800listPDU.getCountMessages();
}
unsigned char * getCentrSMSmessage(const unsigned char numMessage) { // вытаскиваем номер центра сообщения из СМС
return SIM800listPDU.getRawCentrSMSmessage(numMessage);
}
unsigned char * getSenderSMSmessage(const unsigned char numMessage) { // вытаскиваем отправителя из СМС
return SIM800listPDU.getSender(numMessage);
}
unsigned char * getTextSMSmessage(const unsigned char numMessage) { // вытаскиваем текст из СМС
return SIM800listPDU.getText(numMessage);
}
bool isMultiPart(const unsigned char numMessage) { // многоблочное сообщение
return SIM800listPDU.getRawIsMulti(numMessage);
}
bool isFullMessage(const unsigned char numMessage) { // многоблочное ссобщение полностью загрузилось
return SIM800listPDU.isFullMessageGet(numMessage);
}
unsigned char * getTextMultyMessage(const unsigned char numMessage) { // вытаскиваем текст из многоблочного СМС
static unsigned char outStrMultiMsg[maxLenMultiMessage]; // выходная строка для объединения сообщений
memset((unsigned char *)&outStrMultiMsg[0], '\0', maxLenMultiMessage); // очистим выходную строку
unsigned short currIdLink = SIM800listPDU.getRawIDlinkMessage(numMessage); // сохраняем ИД
if (currIdLink) { // если ИД существует
for (unsigned char i = 0; i < SIM800listPDU.getCountMessages(); ++i) { // цикл по всем ссобщениям
if (SIM800listPDU.getRawIDlinkMessage(i) == currIdLink) strcat((char *)&outStrMultiMsg[0], (const char *)SIM800listPDU.getText(i)); // если ИД совпало - прибавляем текст СМС к общему
}
}
return (unsigned char *)&outStrMultiMsg[0]; // возвращаем строку
}
void deleteMultiMessage(const unsigned char numMessage) { // удаление многоблочного сообщения с нужным номером // по факту удаляет все с нужным ИД
unsigned short currIdLink = SIM800listPDU.getRawIDlinkMessage(numMessage); // сохраняем ИД
lab1f: bool isFound = false; // флаг что нашлись сообщения с нужным ИД
for (unsigned char i = 0; i < SIM800listPDU.getCountMessages(); ++i) { // цикл по всем ссобщениям
if (SIM800listPDU.getRawIDlinkMessage(i) == currIdLink) { // если ид совпало
SIM800listPDU.deleteMessage(i); // удаляем сообщение
isFound = true; // ставим флаг что нашли
break; // прерываем цикл по сообщениям
}
}
if (isFound) goto lab1f; // если нашли - заново запускаем поиск по всем смс
}
const char* translitTable90BF[] = {"A", "B", "V", "G", "D", "E", "ZH", "Z"
, "I", "J", "K", "L", "M", "N", "O", "P"
, "R", "S", "T", "U", "F", "H", "C", "CH"
, "SH", "SHH", "\"", "Y", "'", "JE", "YU", "JA"
, "a", "b", "v", "g", "d", "e", "zh", "z"
, "i", "j", "k", "l", "m", "n", "o", "p"};
const char* translitTable808F[] = {"r", "s", "t", "u", "f", "h", "c", "ch"
, "sh", "shh", "\"", "y", "'", "je", "u", "ja"};
unsigned char * convertRusUTF8toASCIItranslit(const unsigned char * inUTF8) { // Транлитерация строки для отображения в логе не поддерживающим UTF8
static unsigned char outStrTranslit[maxLenMultiMessage]; // выходная строка
unsigned char outOne[2];
outOne[1] = '\0';
unsigned short inPos = 0;
memset((unsigned char *)&outStrTranslit[0], '\0', maxLenMultiMessage); // очистим выходную строку
while ((*(inUTF8 + inPos)) > 0) {
unsigned char iB = *(inUTF8 + inPos);
unsigned char iN = *(inUTF8 + inPos + 1);
if (iB == 0xD0) {
if ((iN >= 0x90) && (iN <= 0xBF)) {
strcat((char*)&outStrTranslit[0], translitTable90BF[iN-0x90]);
++inPos;
} else if (iN == 0x81) {
strcat((char*)&outStrTranslit[0], (const char *)"JO");
++inPos;
} else {
outOne[0] = iB;
strcat((char*)&outStrTranslit[0], (const char *)&outOne[0]);
}
} else if (iB == 0xD1) {
if ((iN >= 0x80) && (iN <= 0x8F)) {
strcat((char*)&outStrTranslit[0], translitTable808F[iN-0x80]);
++inPos;
} else if (iN == 0x91) {
strcat((char*)&outStrTranslit[0], (const char *)"jo");
++inPos;
} else {
outOne[0] = iB;
strcat((char*)&outStrTranslit[0], (const char *)&outOne[0]);
}
} else if (iB) {
outOne[0] = iB;
strcat((char*)&outStrTranslit[0], (const char *)&outOne[0]);
}
++inPos;
}
return (unsigned char *)&outStrTranslit[0]; // возвращаем строку
}
void setSCAnumberMNO(unsigned char * inNumber) { // установка номера центра сообщений оператора
SIM800sendPDU.setSCAnumber(inNumber);
}
void sendUTF8toQueueSMSPDU(const unsigned char * inTextSender,
const typeMessageClass inMessageClass, const unsigned char * inMessageText) { // добавить СМС в очередь на отправку
SIM800sendPDU.addUTF8toQueueSMSPDU(inTextSender, inMessageClass, inMessageText);
}
bool existSMStoSendPDU(void) { // есть ли что отправлять
return SIM800sendPDU.existSMStoSend();
}
unsigned char returnCountBytesMsgPDU(void) { // возвращает число байт части сообщения
return SIM800sendPDU.returnCountBytesMsg();
}
unsigned char * returnTextMsgPDU(void) { // возвращает часть сообщения на отправку
return SIM800sendPDU.returnTextMsg();
}