Speficiation for DMR SMS Formats

Discussions related to the firmware code development
Post Reply
KY4YI
Posts: 8
Joined: Sat May 04, 2024 1:22 am
Location: North Carolina, USA

Speficiation for DMR SMS Formats

Post by KY4YI » Sun May 19, 2024 1:57 pm

Included is a link for a specification for DMR SMS messages for Moto and DMR_Standard format.

https://github.com/carpaldolor/DMRText/ ... ev_1.0.pdf

If there is any interest from the team in supporting SMS in the future, I would be willing to assist and provide a module with some low level SMS formatting, decoding and error checking methods.

KY4YI
Posts: 8
Joined: Sat May 04, 2024 1:22 am
Location: North Carolina, USA

Re: Speficiation for DMR SMS Formats

Post by KY4YI » Fri May 24, 2024 12:53 am

Here is a reference implementation for sending a text message. I have tested this with the BF DM-1701 running OpenGD77 and on the receiving side a stock TYT MD-UV390+ and a BTECH 6x2. It provides an example for sending a test from opengd77 in Moto format. I have not included the hookup code since it is just hacked into the mmdvm modem function at this point.

Code: Select all

/*
 * Copyright (C) 2024 John Finn KY4YI
 *
 * Low level DMR stream implementation informed by code written by
 *                         DSD Author (anonymous)
 *                         Ian Wraith G7GHH
 *                         Jonathan Naylor G4KLX
 *
 *
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation,
 *    incorporation in a commercial product or incorporation into a product or project which allows commercial use.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <functions/dmrData.h>
#include "user_interface/uiHotspot.h"
#include "hardware/HR-C6000.h"
#include "functions/ticks.h"

#include <string.h>

#include <stdint.h>
#include <stdbool.h>

void createPreambleCSBK(uint8_t *ret, int src, int dst, bool isGroup, int blocksToFollow);
void createDataHeader(uint8_t *ret, int src, int dst, uint8_t group, uint8_t resp, int blocks, int pad);
void createTextMessage(uint8_t *ret, uint32_t src, uint32_t dst, uint8_t group, uint8_t * msg, int blocks);
void addCRC(uint8_t *in, uint8_t * mask);
static void itu_crc16(uint8_t *in, int len);
static uint32_t itu_crc32(uint8_t *data, int length);

const uint8_t CSBK_CRC_MASK[] = {0xA5, 0xA5};
const uint8_t DATAHEAD_CRC_MASK[] =  {0xCC, 0xCC};

//storage for 10 halfRate PDUs
static uint8_t ipData[120] ;
static uint8_t expCnt = 0 ;
static uint8_t fragCnt = 0 ;
static uint8_t textMsg[32] = {0} ;
static uint16_t sequence = 1;


//Create a response Text Message
static void prepareTextData(uint8_t * msg_rec) {
	uint8_t data[12];
	int src = 1001;
	int dst = 1;
	char msg[32] ;
	sprintf(msg,"Rcvd %d bytes",strlen(msg_rec));

	int len = strlen(msg)*2+38+4;//total IP+crc32
	int pad = 12 - (len%12);
	len+=pad;
	int blocks = len/12;

	int cnbk_cnt = 8;
	int csbk_start = cnbk_cnt + blocks + 1;
	for(int i=0;i<=cnbk_cnt;i++) {
		createPreambleCSBK(data, src,dst,true,csbk_start--) ;
		HRC6000DataAddToTransmitBuffer(DMR_DATA_CSBK,12,data );
	}

	createDataHeader(data, src,dst,1,0,blocks,pad) ;
	HRC6000DataAddToTransmitBuffer(DMR_DATA_HEADER,12,data );

	createTextMessage(ipData, src, dst, 1,  msg, blocks);
	int crc_offset = blocks*LC_DATA_LENGTH-4;
	uint32_t crc32 =  itu_crc32(ipData,crc_offset);
	ipData[crc_offset++] = crc32 ;
	ipData[crc_offset++] = crc32>>8 ;
	ipData[crc_offset++] = crc32>>16 ;
	ipData[crc_offset++] = crc32>>24 ;

	for(int i=0;i<blocks;i++) {
		HRC6000DataAddToTransmitBuffer(DMR_DATA_RATE_1_2,12,&ipData[i*12] );
	}

	//hrc6000TransitionToTx();
	addTimerCallback(sendData, 2000, -1, false);

}

void sendData() {
	startTx();
	hrc6000TransitionToTx();
}

// Handle new incoming text message
static void receivedTextMsg(uint8_t * msg) {
	uiUpdateText(msg); //added to save the last text message
	uiHotspotUpdateScreen(0);

	prepareTextData(msg);
}

//assemble fragments for incoming text message
void dmrDataHandler( uint8_t * frameBuf, int d_type) {

	//logic to assemble IP fragments with no checking for service type text, dmr id, data type, or anything
	//basically assume the only data is moto text
	if(d_type==0x06 ) {
		//process data header
		expCnt = frameBuf[8]&0x7F;
		if(expCnt>10) expCnt=0; //abort,too long
		fragCnt=0;

	} else if (d_type==0x07 && expCnt>0) {
		//process halfrate data

		memcpy(&ipData[fragCnt*LC_DATA_LENGTH],frameBuf, LC_DATA_LENGTH);

		if(++fragCnt>=expCnt) {
			//process the whole message
			expCnt=0;
			int len = (ipData[25]-18)/2 ; //double byte text length
			if( len<32) {//sanity check
				for(int i=0;i<len;i++) {
					textMsg[i] = ipData[38+i*2];
				}
				textMsg[len]=0;
				receivedTextMsg(textMsg);
			}
		}
	}
}

//create a text mssage in the IP UDP Moto format using half rate data
void createTextMessage(uint8_t *ret, uint32_t src, uint32_t dst, uint8_t group, uint8_t * msg, int blocks) {
	for(int i=0;i<(blocks*LC_DATA_LENGTH);i++) ret[i]=0;
	msg[31]=0;

	//UDP Start src/dst port 0x0FA7 for moto text
	ret[20]=0x0F;
	ret[21]=0xA7;
	ret[22]=0x0F;
	ret[23]=0xA7;
	uint32_t len = strlen(msg)*2+18;
	ret[24] = len>>8;
	ret[25] = len;
	ret[29] = len-10;
	ret[30] = 0xA0;
	ret[32] = 0x80 | sequence;
	ret[33] = 0x04;
	ret[34] = 0x0D;
	ret[36] = 0x0A;
	for(int i=0;i<strlen(msg);i++ ) {
		ret[38+i*2] = msg[i];
	}

	//fake IP header for UDP checksum
	ret[8] = 0x0C;
	ret[9] = src>>16;
	ret[10] = src>>8;
	ret[11] = src;
	if(group)
		ret[12] = 0xE1;
	else
		ret[12] = 0x0C;
	ret[13] = dst>>16;
	ret[14] = dst>>8;
	ret[15] = dst;
	ret[17] = 0x11; //UDP Protocol Id
	ret[18] = len>>8; //udp len
	ret[19] = len;

	uint32_t cs = ipChecksum(ret,8,len+12); //UDP checksum
	ret[26]=cs>>8;
	ret[27]=cs;

	//real IP header - shift IP addrs
	memcpy(&ret[16],&ret[12], 4);
	memcpy(&ret[12],&ret[8], 4);
	ret[0]=0x45;
	ret[2]=(len+20)>>8;
	ret[3]=len+20;
	ret[4]=sequence>>8;
	ret[5]=sequence++;
	ret[8]=0x01; //TTL
	ret[9]=0x11; //UDP Protocol Id
	ret[10]=0;
	ret[11]=0;
	cs = ipChecksum(ret,0,20); //IP checksum

	ret[10]=cs>>8;
	ret[11]=cs;

}

void createDataHeader(uint8_t *ret, int src, int dst, uint8_t group, uint8_t resp, int blocks, int pad) {
	ret[0] = (((uint8_t)group<<7) | (resp<<6) | (pad&0x10) | 0x02) ; //0x02 unconfirmed
	ret[1] = (0x04 << 4) | (pad&0x0F) ; //0x04 SAP type IP
	ret[2] = dst>>16;
	ret[3] = dst>>8;
	ret[4] = dst;
	ret[5] = src>>16;
	ret[6] = src>>8;
	ret[7] = src;
	ret[8] = 0x80 | (blocks&0x7F) ; //0x80 Full packet
	ret[9] = 0x0 & 0x0F ; //frag #0
	addCRC(ret,DATAHEAD_CRC_MASK) ;
 }

void createPreambleCSBK(uint8_t *ret, int src, int dst, bool isGroup, int blocksToFollow) {
	ret[0] = 0xBD ;
	ret[2] = 0x80 ;//0x80 data to follow
	if(isGroup) ret[2] |= 0x40;
	ret[3] = blocksToFollow;
	ret[4] = dst>>16;
	ret[5] = dst>>8;
	ret[6] = dst;
	ret[7] = src>>16;
	ret[8] = src>>8;
	ret[9] = src;
	addCRC(ret,CSBK_CRC_MASK) ;
}

void addCRC(uint8_t *in, uint8_t * mask) {
	itu_crc16(in,12);
	in[10] ^= mask[0] ;
	in[11] ^= mask[1] ;
}

int ipChecksum(uint8_t *in, int start, int length) {
	int sum=0;
	for(int i=0;;) {
		sum+= ( (int)in[start+(i++)] << 8);
		if(i>=length) break;
		sum+= in[start+(i++)];
		if(i>=length) break;
	}
	int cs = (~((sum & 0xFFFF)+(sum >> 16)))&0xFFFF;
	return cs ;
}

const uint16_t CRC16_TABLE[] = {
	0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
	0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
	0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
	0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
	0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
	0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
	0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
	0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
	0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
	0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
	0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
	0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
	0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
	0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
	0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
	0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
	0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
	0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
	0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
	0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
	0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
	0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
	0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
	0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
	0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
	0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
	0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
	0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
	0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
	0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
	0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
	0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 };


static void itu_crc16(uint8_t *in, int len)
{
	uint16_t crc16 = 0;
	for (unsigned i = 0; i < (len - 2); i++)
		crc16 = (crc16 << 8) ^ CRC16_TABLE[(crc16&0xFF) ^ in[i]];
	crc16 = ~crc16;
	in[len-1] = crc16 >> 8;
	in[len-2] = crc16;
}


const uint32_t CRC32_TABLE[] = {
    0x00000000,0x04C11DB7,0x09823B6E,0x0D4326D9,
    0x130476DC,0x17C56B6B,0x1A864DB2,0x1E475005,
    0x2608EDB8,0x22C9F00F,0x2F8AD6D6,0x2B4BCB61,
    0x350C9B64,0x31CD86D3,0x3C8EA00A,0x384FBDBD,
    0x4C11DB70,0x48D0C6C7,0x4593E01E,0x4152FDA9,
    0x5F15ADAC,0x5BD4B01B,0x569796C2,0x52568B75,
    0x6A1936C8,0x6ED82B7F,0x639B0DA6,0x675A1011,
    0x791D4014,0x7DDC5DA3,0x709F7B7A,0x745E66CD,
    0x9823B6E0,0x9CE2AB57,0x91A18D8E,0x95609039,
    0x8B27C03C,0x8FE6DD8B,0x82A5FB52,0x8664E6E5,
    0xBE2B5B58,0xBAEA46EF,0xB7A96036,0xB3687D81,
    0xAD2F2D84,0xA9EE3033,0xA4AD16EA,0xA06C0B5D,
    0xD4326D90,0xD0F37027,0xDDB056FE,0xD9714B49,
    0xC7361B4C,0xC3F706FB,0xCEB42022,0xCA753D95,
    0xF23A8028,0xF6FB9D9F,0xFBB8BB46,0xFF79A6F1,
    0xE13EF6F4,0xE5FFEB43,0xE8BCCD9A,0xEC7DD02D,
    0x34867077,0x30476DC0,0x3D044B19,0x39C556AE,
    0x278206AB,0x23431B1C,0x2E003DC5,0x2AC12072,
    0x128E9DCF,0x164F8078,0x1B0CA6A1,0x1FCDBB16,
    0x018AEB13,0x054BF6A4,0x0808D07D,0x0CC9CDCA,
    0x7897AB07,0x7C56B6B0,0x71159069,0x75D48DDE,
    0x6B93DDDB,0x6F52C06C,0x6211E6B5,0x66D0FB02,
    0x5E9F46BF,0x5A5E5B08,0x571D7DD1,0x53DC6066,
    0x4D9B3063,0x495A2DD4,0x44190B0D,0x40D816BA,
    0xACA5C697,0xA864DB20,0xA527FDF9,0xA1E6E04E,
    0xBFA1B04B,0xBB60ADFC,0xB6238B25,0xB2E29692,
    0x8AAD2B2F,0x8E6C3698,0x832F1041,0x87EE0DF6,
    0x99A95DF3,0x9D684044,0x902B669D,0x94EA7B2A,
    0xE0B41DE7,0xE4750050,0xE9362689,0xEDF73B3E,
    0xF3B06B3B,0xF771768C,0xFA325055,0xFEF34DE2,
    0xC6BCF05F,0xC27DEDE8,0xCF3ECB31,0xCBFFD686,
    0xD5B88683,0xD1799B34,0xDC3ABDED,0xD8FBA05A,
    0x690CE0EE,0x6DCDFD59,0x608EDB80,0x644FC637,
    0x7A089632,0x7EC98B85,0x738AAD5C,0x774BB0EB,
    0x4F040D56,0x4BC510E1,0x46863638,0x42472B8F,
    0x5C007B8A,0x58C1663D,0x558240E4,0x51435D53,
    0x251D3B9E,0x21DC2629,0x2C9F00F0,0x285E1D47,
    0x36194D42,0x32D850F5,0x3F9B762C,0x3B5A6B9B,
    0x0315D626,0x07D4CB91,0x0A97ED48,0x0E56F0FF,
    0x1011A0FA,0x14D0BD4D,0x19939B94,0x1D528623,
    0xF12F560E,0xF5EE4BB9,0xF8AD6D60,0xFC6C70D7,
    0xE22B20D2,0xE6EA3D65,0xEBA91BBC,0xEF68060B,
    0xD727BBB6,0xD3E6A601,0xDEA580D8,0xDA649D6F,
    0xC423CD6A,0xC0E2D0DD,0xCDA1F604,0xC960EBB3,
    0xBD3E8D7E,0xB9FF90C9,0xB4BCB610,0xB07DABA7,
    0xAE3AFBA2,0xAAFBE615,0xA7B8C0CC,0xA379DD7B,
    0x9B3660C6,0x9FF77D71,0x92B45BA8,0x9675461F,
    0x8832161A,0x8CF30BAD,0x81B02D74,0x857130C3,
    0x5D8A9099,0x594B8D2E,0x5408ABF7,0x50C9B640,
    0x4E8EE645,0x4A4FFBF2,0x470CDD2B,0x43CDC09C,
    0x7B827D21,0x7F436096,0x7200464F,0x76C15BF8,
    0x68860BFD,0x6C47164A,0x61043093,0x65C52D24,
    0x119B4BE9,0x155A565E,0x18197087,0x1CD86D30,
    0x029F3D35,0x065E2082,0x0B1D065B,0x0FDC1BEC,
    0x3793A651,0x3352BBE6,0x3E119D3F,0x3AD08088,
    0x2497D08D,0x2056CD3A,0x2D15EBE3,0x29D4F654,
    0xC5A92679,0xC1683BCE,0xCC2B1D17,0xC8EA00A0,
    0xD6AD50A5,0xD26C4D12,0xDF2F6BCB,0xDBEE767C,
    0xE3A1CBC1,0xE760D676,0xEA23F0AF,0xEEE2ED18,
    0xF0A5BD1D,0xF464A0AA,0xF9278673,0xFDE69BC4,
    0x89B8FD09,0x8D79E0BE,0x803AC667,0x84FBDBD0,
    0x9ABC8BD5,0x9E7D9662,0x933EB0BB,0x97FFAD0C,
    0xAFB010B1,0xAB710D06,0xA6322BDF,0xA2F33668,
    0xBCB4666D,0xB8757BDA,0xB5365D03,0xB1F740B4} ;

	static uint32_t itu_crc32(uint8_t *data, int length) {
		uint32_t crc = 0;
		for(int i=0;i<length;i+=2){
		      crc = CRC32_TABLE[data[i+1] ^ ((crc >> 24) & 0xff)] ^ (crc << 8);
		      crc = CRC32_TABLE[data[i] ^ ((crc >> 24) & 0xff)] ^ (crc << 8);
		}
		return crc;
	}


Post Reply