Библиотека чтения SMS в PDU формате STM32

Библиотека чтения SMS в PDU формате. STM32.

Отправка сообщений пока в работе, будет выложена здесь же.
Занимает много памяти, тестировал на STM32F411CEU6.

Использование - ввод пришедшей СМС

    		addPduMessageWithLenBody(inString, smsDataLenField);

Кусок кода, парсинг входящей строки модема

Спойлер
void parsingInLinePDU(const unsigned char *inString) { // основная функция, вытаскивающая текст SMS PDU
    static unsigned char smsDataIndex = 0; // индекс смс
    static unsigned char smsDataLenField = 0; // длина данных из заголовка
    constexpr unsigned char lenKeyWord = 7; // длина "+CMGL: "
    unsigned char * p1 = (unsigned char *)strstr((const char *) inString, (const char *) "+CMGL: "); // ищем ключевое слово заголовка
    if ((p1) && (p1 == inString) && (strlen((const char *)inString) > 14)) { // нашелся заголовок в начале строки
    	smsDataIndex = 0; // сброс индекса СМС
    	smsDataLenField = 0; // сброс длины поля СМС данных
    	p1 += lenKeyWord; // переходим в поле индекса СМС
    	if (isdigit(*p1)) { // смотрим первую цифру индекса
    		smsDataIndex = (*p1) - '0'; // сохранили первую цифру индекса
    		++p1; // перешли на след символ
    		if (isdigit(*p1)) { // смотрим вторую цифру индекса
    			smsDataIndex *= 10; // первая цифра индекса была десятки
    			smsDataIndex += (*p1) - '0'; // прибавляем вторую цифру индекса
    			++p1; // перешли на след символ
    		}
    		if (((*p1) == ',') && (isdigit(*(p1+1))) && ((*(p1+2)) == ',') && ((*(p1+3)) == '\"')) { // нашлась запятая - след поле статус - потом цифра - потом кавычки // +CMGL: 6,1,"",159
    			p1 += 4; // сдвигаем указатель
    			unsigned char * p2 = (unsigned char *)strchr((const char *) p1, '\"'); // получаем указатель на след кавычку
    			if ((p2 >= p1) && ((*(p2+1)) == ',') && (isdigit(*(p2+2)))) { // есть вторая кавычка, далее запятая, потом цифры
    				p2 += 2; // переходим на поле с цифрами
    				smsDataLenField = (*p2) - '0'; // сохранили первую цифру длины данных
    	    		++p2; // перешли на след символ
    	    		if (isdigit(*p2)) { // смотрим вторую цифру длины
    	    			smsDataLenField *= 10; // первая цифра длины была десятки
    	    			smsDataLenField += (*p2) - '0'; // прибавляем вторую цифру длины
    	    			++p2; // перешли на след символ
        	    		if (isdigit(*p2)) { // смотрим третью цифру длины
        	    			smsDataLenField *= 10; // вторая цифра длины была десятки
        	    			smsDataLenField += (*p2) - '0'; // прибавляем третью цифру длины
        	    		}
    	    		}
    	    		if ((smsDataIndex) && (smsDataLenField)) { // нормально получили индекс и длину
#ifdef SHOW_SMS_LOG
    	    			printf("Idx %d\n",smsDataIndex);
    	    			printf("L head %d\n",smsDataLenField);
#endif
    	    		} else { // какая то хрень в строке
            	    	smsDataIndex = 0; // сброс индекса СМС
            	    	smsDataLenField = 0; // сброс длины поля СМС данных
            		}
    			} else { // какая то хрень в строке
        	    	smsDataIndex = 0; // сброс индекса СМС
        		}
    		} else { // какая то хрень в строке
    	    	smsDataIndex = 0; // сброс индекса СМС
    		}
    	}
    } else { // в начале строки отсутствует заголовок
    	if ((smsDataIndex) && (smsDataLenField) && (strlen((const char *)inString) > smsDataLenField)) { // из заголовка вытащены данные - всю строку отправляем во внешнюю обработку
    		addPduMessageWithLenBody(inString, smsDataLenField);
    	} else { // кривой заголовок // все заново
        	smsDataIndex = 0; // сброс индекса СМС
        	smsDataLenField = 0; // сброс длины поля СМС данных
    	}
    }
}

Лог модема SIM800L:

Спойлер

Start! CLK = 99999984
RCC->CR 0x3037c83
RCC->CFGR 0x100a
RCC->PLLCFGR 0x2440180c
DS18B20 ready
start modem units
loop begin
ATZ

OK
ATE0
ATE0

OK
ATV1

OK
AT+CMEE=2

OK
AT+CLIP=1

OK
ATS0=0

OK
AT+CMGF=0

OK
AT+CSCLK=0

OK
AT+CREG?

+CREG: 0,1

OK
AT+CSPN?

+CSPN: “MegaFon”,0
MegaFon

OK
AT+COPS?

+COPS: 0,0,“MegaFon”
MegaFon

OK
AT+CSQ

+CSQ: 18,0
18

OK
AT+CLVL=50

OK
AT+CBC
AT+CBC

+CBC: 0,99,4197
4197

OK
AT+CMGL=4,0

+CBC: 0,99,4197

OK
AT+CMGDA=6

+CMGL: 1,0,“”,32
07919702926033F1240B919702369149F60000522072516570210E53F45B4E07B5CBF379F85C7601

OK
7920****946 Short message.

OK
AT+CMGL=4,0

OK
AT+CMGDA=6

OK
AT+CMGL=4,0

OK
AT+CMGDA=6

OK
t = 26.000000

+CMTI: “SM”,1

+CMTI: “SM”,2
AT+CMGL=4,0

+CMGL: 1,0,“”,159
07919702926033F1440B919702369149F6000052207251655321A00500035E02019CEF31FAC402D5D9E971980532BFDD61F989050AC3E9E57598051297E7F3767ECE2EBBDD7935280DA2D7E76B765E0D9ADBCB7417488B4EDBD3A0F21C8D56BF41E837FD041AA3CB747B594E3F81ECE57518D402D9E7EA37485C2797E9207A78ED0225E7E837390C7297E92E50B52D57BFE7E813A8057287C768B7FA3D479F406FB83A4C3F81E6

+CMGL: 2,0,“”,117
07919702926033F1640B919702369149F60000522072516553216F0500035E0202DCE1313ACC0EB3406910FC6DA7BFE569FA5C1D06D9E7EA370BB40EAF41F6393D2C3FE940EEF7187D6281D865B23AEC0EABC320B93A2C3E81D66177981D6681C2707A791D6681EAECF438CC0299DFEEB0FCE40231DFEE3368DA9C8600

OK
AT+CMGDA=6

OK
7920****946 Noch’, ulica, fonar’, apteka, bessmyslennyj i tusklyj svet. Zhivi eshhjo hot’ chetvert’ veka - vsjo budet tak. Ishoda net. Umrjosh’ - nachnjosh’ opjat’ snachala, i povtoritsja vsjo, kak vstar’: noch’, ledjanaja rjab’ kanala, apteka, ulica, fonar’. Long SMS!
AT+CMGL=4,0

OK
AT+CMGDA=6

OK
t = 26.000000

Использование - чтение и/или отправка пришедшего сообщения в обработку

Спойлер
			// вывести в лог прочитанные сообщения в расшифрованном формате
			// поскольку указатели на сообщение после удаления протухают
			// и мы не можем сразу удалить сообщение после отображения (оно не быстрое)
			// выводить будем по одному сообщению (нулевому)
			// а при следующем проходе удалять его
			static unsigned long timerReadSMS = 0UL; // таймер чтения смс из памяти МК
			static bool delReaderMsg = false; // флаг что удаляем
			if (((millis() - timerReadSMS) >= 3789UL) && (countPduMessages())) { // время пришло и сообщения есть
				timerReadSMS = millis(); // сброс таймера
				if (isMultiPart(0)) { // если сообщение многоблочное
					if (isFullMessage(0)) { // ждем пока все части загрузятся
						if (delReaderMsg) { // если надо удалить
							deleteMultiMessage(0); // удаляем многоблочное
							delReaderMsg = false; // сбрасываем флаг удаления
						} else { // если надо обработать
							printf("#RED#%s  %s\r\n", getSenderSMSmessage(0), getTextMultyMessage(0)); // выводим/обрабатываем
							delReaderMsg = true; // ставим флаг удаления
						}
					}
				} else { // сообщение из одной части
					if (delReaderMsg) {
						deletePduMessage(0);
						delReaderMsg = false;
					} else {
						printf("#RED#%s  %s\r\n", getSenderSMSmessage(0), getTextSMSmessage(0));
						delReaderMsg = true;
					}
				}
			}

Первый файл заголовочный

Спойлер
/*
 * 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

constexpr unsigned short maxLenMultiMessage = 512; // максимальный размер текста многоблочной СМС, используется для вывода и/или обработки getTextMultyMessage

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; // длина строки отправителя/получателя

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>

listSMSbyPDU SIM800listPDU; // объект класса всех сообщений устройства

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 формата
	unsigned char outPos = 0; // индекс позиция выходной строки
#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); // читаем байт
		if (iB) { // если он есть
			*(outStr + outPos) = iB; // заносим его в выходную строку
			++outStr; // увеличиваем выходной счетчик
			*(outStr + outPos) = '\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; // если нашли - заново запускаем поиск по всем смс
}

Литература

Спойлер

Raspberry Pi Pico | Аппаратная платформа Arduino
Embedded Pro. Отправка длинных SMS в формате PDU.
Декодирование SMS-сообщений в формате PDU | Хард / Софт
Отправка SMS-сообщений в формате PDU | Хард / Софт
Send and Receive Concatenated SMS in PDU mode | Chatak

Почему не гитхаб?

Не умею. Не требуется. Опять же по хорошему там много строк можно оптимизировать. А я не настоящий программист. Цель стояла написать понятный код с максимальным количеством комментариев, а не красиво выложить куда то.

2 лайка

Добавлена функция транслитерации из UT-8 для отображаения где либо русского текста, не поддерживающего соотвествующую кодировку.

unsigned char * convertRusUTF8toASCIItranslit(const unsigned char * inUTF8);

Исправленый несколько незначительных ошибок.

Спойлер
/*
 * 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

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; // длина строки отправителя/получателя

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>

listSMSbyPDU SIM800listPDU; // объект класса всех сообщений устройства

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]; // возвращаем строку
}

Отправка сообщений, пока латиницы и только до 160 символов включительно.
Отправка русских сообщений в процесса.

Добавление сообщений в очередь отправки:

Спойлер
	// прописать номер центра сообщения оператора
	// в дальнейшем его можно брать из приходящих СМС
	// функцией unsigned char * getCentrSMSmessage(const unsigned char numMessage); // вытаскиваем номер центра сообщения из СМС
	setSCAnumberMNO((unsigned char*)"79202906331");
	// короткое сообщение латиницей // добавляем сообщение в очередь отправки
	sendUTF8toQueueSMSPDU((const unsigned char *)"7920*****46", classMessageNormal, (const unsigned char *)"Hello!! two");
	// длинное сообщение, обрежется до 160 символов // добавляем сообщение в очередь отправки
	sendUTF8toQueueSMSPDU((const unsigned char *)"7920*****46", classMessageNormal, (const unsigned char *)"This eBook is for the use of anyone anywhere in the United States and most other parts of the world at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project Gutenberg License included with this eBook or online.");
	// короткое всплывающее СМС // добавляем сообщение в очередь отправки
	sendUTF8toQueueSMSPDU((const unsigned char *)"7920*****46", classMessageFlash, (const unsigned char *)"Fake message!");

Непосредственно отправка в модем, когда он ничем не занят:

	            			if (existSMStoSendPDU()) { // есть что отправлять
	            				// отправляем в модем количество байт на отправку и само сообщение
	            				// returnCountBytesMsgPDU();
	            				// returnTextMsgPDU();
	            				// реализация зависит от самого кода работы с модемом
	            				rawPDUSMSsend(returnCountBytesMsgPDU(), returnTextMsgPDU());
	            			}

Результат лога модема:

Спойлер

Start! CLK = 99999984
RCC->CR 0x3037c83
RCC->CFGR 0x100a
RCC->PLLCFGR 0x2440180c
DS18B20 ready
start modem units
loop begin
ATZ

OK
ATE0
ATE0

OK
ATV1

OK
AT+CMEE=2

OK
AT+CLIP=1

OK
ATS0=0

OK
AT+CMGF=0

OK
AT+CSCLK=0

OK
AT+CREG?

+CREG: 0,1

OK
AT+CSPN?

+CSPN: “MegaFon”,0
MegaFon

OK
AT+COPS?

+COPS: 0,0,“MegaFon”
MegaFon

OK
AT+CSQ

+CSQ: 25,0
25

OK
AT+CLVL=50

OK
AT+CBC
AT+CBC

+CBC: 0,89,4120
4120

OK
AT+CMGL=4,0

+CBC: 0,89,4120

OK
AT+CMGDA=6

OK
AT+CMGS=24

OK

07919702926033F111000B919702369149F600000B0BC8329BFD0E8540F4FB1B
AT+CMGL=4,0

+CMGS: 236

OK
AT+CMGDA=6

OK
AT+CMGL=4,0

OK
AT+CMGDA=6

OK

+CMTI: “SM”,1
AT+CMGL=4,0

+CMGL: 1,0,“”,31
07919702926033F1240B919702369149F60008523070410473210C041F04400438043204350442

OK
AT+CMGDA=6

OK
7920*****46 Privet
AT+CMGS=154

07919702926033F111000B919702369149F600000BA054747A0E2A0BDFEF35283D0799DF72101D5D06D5E765D0DB0C0ABBF36F77191476E7EFE8B2BC0C4ABB417474195475A7E96532684A0FD3CB7350D84D06B5DF733AE84D4797E52078584E9F83DE66101D5D06DDDF72361914A683DC6FD0F83DA783C26E32E89EA6A3416176FB3DA783DC6F90BC3CA7CBD3637AFAED9E83EEE8307DFE2EDBCB721728FBAE83DA
AT+CMGL=4,0

+CMGS: 237

OK
AT+CMGDA=6

OK
AT+CMGL=4,0

OK
AT+CMGDA=6

OK
AT+CMGL=4,0

OK
AT+CMGDA=6

OK
t = 25.500000
AT+CMGS=26

07919702926033F111000B919702369149F600100B0DC6F0BA0C6A97E7F3F0B91C02

+CMGS: 238

OK
AT+CMGL=4,0

OK
AT+CMGDA=6

OK
AT+CMGL=4,0

OK
AT+CMGDA=6

OK
t = 25.500000

код

Спойлер
/*
 * 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

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);
	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 = 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 (this->_firstPart->_nextPart) {
					nP = this->_firstPart->_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>

listSMSbyPDU SIM800listPDU; // объект класса всех сообщений устройства
sendListPDU SIM800sendPDU; // объект класса всех отправляемых сообщений

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);
}

void sendItemPDU::_convertPDUtoParts(void) {
	unsigned short i = 0; // индкс по символам
	while (*(this->_textMessage + i)) { // цикл по входящей строке
		if ((*(this->_textMessage + i) < 0x20) || (*(this->_textMessage + i) >= 0x7F)) { // not ASCII
			printf("%02X\r\n", *(this->_textMessage + i));
			this->_textFormat = messageUCS2;
			break;
		}
		++i;
	}
	if (this->_textFormat == messageUCS2) {
		// -
	} else {
		this->_convert7bitsToOnePartPDU();
	}
	this->_isConverted = true;
}

unsigned char sendItemPDU::returnCountBytesMsgRaw(void) {
	if (!this->_isConverted) this->_convertPDUtoParts();
	if ((this->_countPartsAll) && (this->_firstPart)) return this->_firstPart->_countBytesToOut;
	return 0;
}

unsigned char * sendItemPDU::returnTextMsgRaw(void) {
	if (!this->_isConverted) this->_convertPDUtoParts();
	if ((this->_countPartsAll) && (this->_firstPart)) {
		++this->_countSendParts;
		return	this->_firstPart->_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();
}

1 лайк

Доделал русифицированную отправку.

Применение аналогичное.

	// сообщение русское // добавляем сообщение в очередь отправки
	sendUTF8toQueueSMSPDU((const unsigned char *)"7920*****46", classMessageNormal, (const unsigned char *)"Питание=ОТКЛ Температура=-127.00 Внешн.вход=НЕТ Uptime=0 h.  Тут далее добавим текста, чтобы получилось более 70 chars.");

код

Спойлер
/*
 * 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();
}

Схема и фото как это все в реальном железе работает.


1 лайк