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.
Speficiation for DMR SMS Formats
Re: Speficiation for DMR SMS Formats
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;
}