#!/usr/bin/env python3 """Transmit an SMS from the radio itself. The burst mechanism is the one already proven in hotspot mode: the 12 byte payload goes to register page 0x02 and reg 0x50 selects the burst type, and the HR-C6000 builds the FEC and sync around it. What is new here is driving it from normal radio operation, where the state machine is otherwise built around voice. A transmission is queued as a list of bursts (preambles, data header, then the rate 1/2 blocks). The timeslot interrupt pulls one burst per active slot until the queue empties, then ends the transmission. """ W = "/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application" # --------------------------------------------------------------- sms.h p = W + "/include/functions/sms.h" s = open(p).read() if "smsSendText" in s: print("sms.h: already declared") else: s = s.replace("uint8_t smsGetBlockCount(uint16_t packetLength);", """// Queue a text message for transmission. Returns false if a transmission is already // in progress or the message does not fit. bool smsSendText(uint32_t dstId, bool isGroup, const char *text); // True while bursts are still waiting to go out. bool smsTxIsActive(void); // Called from the timeslot interrupt: fetch the next burst to send. // Returns false when the transmission is finished. bool smsTxGetNextBurst(uint8_t *payload, uint8_t *dataType); uint8_t smsGetBlockCount(uint16_t packetLength);""", 1) open(p, "w").write(s) print("sms.h: transmit API declared") # --------------------------------------------------------------- sms.c p = W + "/source/functions/sms.c" s = open(p).read() if "smsSendText" in s: print("sms.c: already has transmit") else: s = s.replace('#include "functions/ticks.h"\n', '#include "functions/ticks.h"\n#include "functions/trx.h"\n#include "functions/settings.h"\n', 1) s += """ // ---------------------------------------------------------------- transmit #define SMS_TX_MAX_BURSTS 24U // 4 preambles + header + up to 19 blocks #define SMS_PREAMBLE_COUNT 4U static uint8_t txBursts[SMS_TX_MAX_BURSTS][SMS_BLOCK_LENGTH]; static uint8_t txTypes[SMS_TX_MAX_BURSTS]; static uint8_t txCount = 0; static uint8_t txIndex = 0; static uint8_t txSeq = 0x81; bool smsSendText(uint32_t dstId, bool isGroup, const char *text) { uint8_t packet[(SMS_MAX_TEXT_LENGTH * 2) + 64]; uint8_t pad = 0; uint16_t packetLength; uint8_t blocks; uint8_t n = 0; if (txCount != 0) { return false;// a transmission is already queued } packetLength = smsBuildPacket(packet, sizeof(packet), trxDMRID, dstId, isGroup, text, 0xA0, txSeq, &pad);// 0xA0: we are not asking for a delivery report yet if (packetLength == 0) { return false; } blocks = smsGetBlockCount(packetLength); if ((uint8_t)(blocks + SMS_PREAMBLE_COUNT + 1U) > SMS_TX_MAX_BURSTS) { return false;// message too long for the queue } // Preambles first, counting down towards the header, as real radios send them. for (uint8_t i = 0; i < SMS_PREAMBLE_COUNT; i++) { smsBuildPreamble(txBursts[n], trxDMRID, dstId, isGroup, (uint8_t)(SMS_PREAMBLE_COUNT - i)); txTypes[n] = DT_CSBK; n++; } smsBuildDataHeader(txBursts[n], trxDMRID, dstId, isGroup, blocks, pad); txTypes[n] = DT_DATA_HEADER; n++; for (uint8_t i = 0; i < blocks; i++) { smsGetBlock(packet, packetLength, i, txBursts[n]); txTypes[n] = DT_RATE_12_DATA; n++; } txIndex = 0; txCount = n; txSeq = (uint8_t)(0x80U | (((txSeq + 1U) & 0x7FU) ? ((txSeq + 1U) & 0x7FU) : 1U)); smsOutboxAdd(dstId, (isGroup == false), text); // Key the transmitter; the timeslot interrupt will drain the queue. trxTransmissionEnabled = true; trxEnableTransmission(); return true; } bool smsTxIsActive(void) { return (txCount != 0); } bool smsTxGetNextBurst(uint8_t *payload, uint8_t *dataType) { if ((txCount == 0) || (txIndex >= txCount)) { txCount = 0; txIndex = 0; return false; } memcpy(payload, txBursts[txIndex], SMS_BLOCK_LENGTH); *dataType = txTypes[txIndex]; txIndex++; return true; } """ open(p, "w").write(s) print("sms.c: transmit queue added") # --------------------------------------------------------------- HR-C6000.c p = W + "/source/hardware/HR-C6000.c" s = open(p).read() if "smsTxGetNextBurst" in s: print("HR-C6000.c: already patched") else: # In the ongoing-TX slot, send our data bursts instead of voice when a message is queued. anchor = """ case DMR_STATE_TX_2: // Ongoing TX (active timeslot) if (hrc.transmissionEnabled) { """ assert anchor in s, "TX_2 anchor not found" s = s.replace(anchor, """ case DMR_STATE_TX_2: // Ongoing TX (active timeslot) if (hrc.transmissionEnabled) { // A text message queued by the radio itself takes over the slot: its // payload goes to the LC page and reg 0x50 picks the burst type, exactly // as in hotspot mode, while the vocoder path stays idle. if ((settingsUsbMode != USB_MODE_HOTSPOT) && smsTxIsActive()) { uint8_t smsPayload[LC_DATA_LENGTH]; uint8_t smsType; if (smsTxGetNextBurst(smsPayload, &smsType)) { SPI0WritePageRegByteArray(0x02, 0x00, smsPayload, LC_DATA_LENGTH); SPI1WritePageRegByteArray(0x03, 0x00, SILENCE_AUDIO, AMBE_AUDIO_LENGTH); SPI0WritePageRegByte(0x04, 0x41, 0x80); SPI0WritePageRegByte(0x04, 0x50, (uint8_t)(smsType << 4)); } else { // Queue drained: stop keying and let the normal shutdown run. hrc.transmissionEnabled = false; trxTransmissionEnabled = false; } slotState = DMR_STATE_TX_1; break; } """, 1) # And do not announce a voice call in front of the data when we are the sender. s = s.replace("static inline bool isHotspotDataTx(void)\n{\n\treturn ((settingsUsbMode == USB_MODE_HOTSPOT) && (hrc.txDataType != HOTSPOT_TX_TYPE_VOICE));\n}", "static inline bool isHotspotDataTx(void)\n{\n" "\t// Also true while the radio itself is sending a text message, so that the\n" "\t// Voice LC Header is suppressed in front of the data bursts.\n" "\treturn (((settingsUsbMode == USB_MODE_HOTSPOT) && (hrc.txDataType != HOTSPOT_TX_TYPE_VOICE))\n" "\t\t\t|| smsTxIsActive());\n}", 1) open(p, "w").write(s) print("HR-C6000.c: radio-originated data transmission wired in")