Восстановлено из транскрипта сессии после потери рабочего дерева на сервере сборки. Что внутри: - четыре новых модуля прошивки (приём, сборка исходящего, меню Messages); - правки существующих файлов в виде скриптов, с порядком применения; - инструменты стенда: снятие экрана рации по USB, дамп кадров, эталонная сборка пакета; - NOTES.md: что проверено в эфире, что не доделано, где грабли. Работало в эфире в августе 2026: данные ходят в обе стороны в режиме хотспота, рация принимает SMS и показывает на экране. Отправка собрана и сверена с эталоном сервера побайтово, но в эфире не проверялась. Claude-Session: https://claude.ai/code/session_01GJtvG5WwqYNe1b7s9LLtgy
494 строки
21 KiB
Markdown
494 строки
21 KiB
Markdown
# Правки, сделанные командами (не отдельными файлами)
|
|
|
|
Извлечено из транскрипта сессии в порядке применения.
|
|
Каждый блок - самостоятельная команда, менявшая исходники прошивки.
|
|
|
|
## Правка 1 (строка транскрипта 966)
|
|
|
|
```bash
|
|
export SSHPASS='Adelina@2015'
|
|
sshpass -e ssh -o StrictHostKeyChecking=no viktor@10.0.0.200 'python3 - <<"PYEOF"
|
|
W="/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
|
p = W + "/include/functions/hotspot.h"
|
|
s = open(p).read()
|
|
|
|
anchor = "#define DMR_SYNC_DATA 0x40U\n"
|
|
if anchor not in s:
|
|
raise SystemExit("anchor not found in hotspot.h")
|
|
if "DT_RATE_12_DATA" in s:
|
|
raise SystemExit("already patched")
|
|
|
|
add = anchor + """
|
|
// DMR data burst types (ETSI TS 102 361-1, Slot Type "data type" field).
|
|
// The hotspot only ever handled the two voice-related ones; the packet data
|
|
// types below are needed to carry SMS/position data over the air.
|
|
#define DT_VOICE_PI_HEADER 0x00U
|
|
#define DT_CSBK 0x03U
|
|
#define DT_DATA_HEADER 0x06U
|
|
#define DT_RATE_12_DATA 0x07U
|
|
#define DT_RATE_34_DATA 0x08U
|
|
#define DT_IDLE 0x09U
|
|
#define DT_RATE_1_DATA 0x0AU
|
|
#define DT_MASK 0x0FU
|
|
|
|
// Marker used in hotspotTxDataType[] for "this buffered frame is voice, not data".
|
|
#define HOTSPOT_TX_TYPE_VOICE 0xFFU
|
|
"""
|
|
s = s.replace(anchor, add, 1)
|
|
|
|
# expose the per-buffer data-type markers to the HR-C6000 driver
|
|
tail = "extern bool hotspotMmdvmHostIsConnected;\n"
|
|
if tail not in s:
|
|
raise SystemExit("tail anchor not found")
|
|
s = s.replace(tail, tail + """
|
|
// For each frame queued in audioAndHotspotDataBuffer.hotspotBuffer[], this holds either
|
|
// HOTSPOT_TX_TYPE_VOICE or the DMR data type (DT_*) that the frame has to be sent as.
|
|
extern volatile uint8_t hotspotTxDataType[];
|
|
""", 1)
|
|
|
|
open(p, "w").write(s)
|
|
print("hotspot.h patched")
|
|
PYEOF' 2>&1 | tail -5
|
|
```
|
|
|
|
## Правка 2 (строка транскрипта 978)
|
|
|
|
```bash
|
|
export SSHPASS='Adelina@2015'
|
|
sshpass -e ssh -o StrictHostKeyChecking=no viktor@10.0.0.200 'python3 - <<"PYEOF"
|
|
W="/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
|
p = W + "/source/functions/hotspot.c"
|
|
s = open(p).read()
|
|
if "hotspotTxDataType" in s:
|
|
raise SystemExit("already patched")
|
|
|
|
# ---- 1. define the marker array next to the other hotspot state ----
|
|
anchor = "static uint8_t hotspotTxLC[9];\n"
|
|
assert anchor in s, "hotspotTxLC anchor missing"
|
|
s = s.replace(anchor, anchor + """
|
|
// Per-buffered-frame DMR data type. HOTSPOT_TX_TYPE_VOICE means \"ordinary voice frame\",
|
|
// anything else is a packet data burst (SMS etc) that must bypass the vocoder path.
|
|
volatile uint8_t hotspotTxDataType[HOTSPOT_BUFFER_COUNT];
|
|
""", 1)
|
|
|
|
# ---- 2. voice frames queued by storeNetFrame() must be marked as voice ----
|
|
old = """ memcpy((uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_write_idx], hotspotTxLC, 9);// copy the current LC into the data (mainly for use with the embedded data);
|
|
wavbuffer_count++;"""
|
|
assert old in s, "storeNetFrame body not found"
|
|
s = s.replace(old, """ memcpy((uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_write_idx], hotspotTxLC, 9);// copy the current LC into the data (mainly for use with the embedded data);
|
|
hotspotTxDataType[wavbuffer_write_idx] = HOTSPOT_TX_TYPE_VOICE;
|
|
wavbuffer_count++;""", 1)
|
|
|
|
# ---- 3. new function: queue a packet data burst coming from the host ----
|
|
anchor2 = "static uint8_t hotspotModeReceiveNetFrame(const uint8_t *comBuffer, uint8_t timeSlot)\n"
|
|
assert anchor2 in s, "hotspotModeReceiveNetFrame not found"
|
|
s = s.replace(anchor2, """// Queue a packet data burst (CSBK / data header / rate 1/2 payload) received from the
|
|
// MMDVM host, so that the HR-C6000 sends it as a real data burst rather than as audio.
|
|
//
|
|
// The host hands us a complete 33 byte DMR burst (BPTC(196,96) coded). The HR-C6000
|
|
// applies the FEC itself and only wants the 96 bit (12 byte) payload, which is exactly
|
|
// what BPTCdecode() gives us - the same route the radio already uses for LC headers.
|
|
static uint8_t storeNetDataFrame(volatile const uint8_t *comBuffer, uint8_t dataType)
|
|
{
|
|
if (wavbuffer_count >= HOTSPOT_BUFFER_COUNT)
|
|
{
|
|
return 5;// out of space: MMDVMHost treats a non-zero return as a NAK
|
|
}
|
|
|
|
BPTCglobalsInit();
|
|
|
|
taskENTER_CRITICAL();
|
|
BPTCdecode((uint8_t *)comBuffer + MMDVM_HEADER_LENGTH,
|
|
(uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_write_idx]);
|
|
hotspotTxDataType[wavbuffer_write_idx] = dataType;
|
|
wavbuffer_count++;
|
|
wavbuffer_write_idx = ((wavbuffer_write_idx + 1) % HOTSPOT_BUFFER_COUNT);
|
|
taskEXIT_CRITICAL();
|
|
|
|
// Data bursts are short and self-contained, so start transmitting straight away
|
|
// instead of waiting for the audio pre-buffering to fill up.
|
|
if ((hotspotState != HOTSPOT_STATE_TX_START_BUFFERING) && (hotspotState != HOTSPOT_STATE_TRANSMITTING))
|
|
{
|
|
timeoutCounter = TX_BUFFERING_TIMEOUT;
|
|
hotspotState = HOTSPOT_STATE_TX_START_BUFFERING;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
""" + anchor2, 1)
|
|
|
|
# ---- 4. dispatch data bursts before the voice-only decode ----
|
|
old2 = """ lc.srcId = 0;// zero these values as they are checked later in the function, but only updated if the data type is DT_VOICE_LC_HEADER
|
|
lc.dstId = 0;
|
|
"""
|
|
assert old2 in s, "dispatch anchor not found"
|
|
s = s.replace(old2, """ // MMDVM control byte: bit 0x40 marks a data sync burst, low nibble is the DMR data type.
|
|
// Voice LC header / terminator keep their existing handling below; the packet data types
|
|
// have no path at all in stock firmware, which is why SMS never reached the air.
|
|
if ((comBuffer[3] & DMR_SYNC_DATA) == DMR_SYNC_DATA)
|
|
{
|
|
uint8_t dataType = (comBuffer[3] & DT_MASK);
|
|
|
|
switch (dataType)
|
|
{
|
|
case DT_CSBK:
|
|
case DT_DATA_HEADER:
|
|
case DT_RATE_12_DATA:
|
|
return storeNetDataFrame(comBuffer, dataType);
|
|
|
|
default:
|
|
break;// fall through to the voice handling
|
|
}
|
|
}
|
|
|
|
""" + old2, 1)
|
|
|
|
open(p, "w").write(s)
|
|
print("hotspot.c patched")
|
|
PYEOF' 2>&1 | tail -5
|
|
```
|
|
|
|
## Правка 3 (строка транскрипта 1008)
|
|
|
|
```bash
|
|
export SSHPASS='Adelina@2015'
|
|
sshpass -e ssh -o StrictHostKeyChecking=no viktor@10.0.0.200 'python3 - <<"PYEOF"
|
|
W="/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
|
p = W + "/source/hardware/HR-C6000.c"
|
|
s = open(p).read()
|
|
if "txDataType" in s:
|
|
raise SystemExit("already patched")
|
|
|
|
# ---- 1. state field: which DMR type the frame currently in deferredUpdateBuffer must be sent as
|
|
a = "\tvolatile bool hotspotDMRTxFrameBufferEmpty;\n"
|
|
assert a in s
|
|
s = s.replace(a, a + "\tvolatile uint8_t txDataType;// HOTSPOT_TX_TYPE_VOICE, or a DT_* packet data type\n", 1)
|
|
|
|
a = "\t\t.hotspotDMRTxFrameBufferEmpty = true,\n"
|
|
assert a in s
|
|
s = s.replace(a, a + "\t\t.txDataType = HOTSPOT_TX_TYPE_VOICE,\n", 1)
|
|
|
|
# ---- 2. remember the type whenever a frame is pulled out of the hotspot buffer
|
|
a = """ memcpy((uint8_t *)deferredUpdateBuffer, (uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_read_idx], AMBE_AUDIO_LENGTH + LC_DATA_LENGTH);
|
|
// Note:"""
|
|
assert a in s, "first fetch site missing"
|
|
s = s.replace(a, """ memcpy((uint8_t *)deferredUpdateBuffer, (uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_read_idx], AMBE_AUDIO_LENGTH + LC_DATA_LENGTH);
|
|
hrc.txDataType = hotspotTxDataType[wavbuffer_read_idx];
|
|
// Note:""", 1)
|
|
|
|
a = """ memcpy((uint8_t *)deferredUpdateBuffer, (uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_read_idx], AMBE_AUDIO_LENGTH + LC_DATA_LENGTH);
|
|
|
|
wavbuffer_read_idx = ((wavbuffer_read_idx + 1) % HOTSPOT_BUFFER_COUNT);"""
|
|
assert a in s, "second fetch site missing"
|
|
s = s.replace(a, """ memcpy((uint8_t *)deferredUpdateBuffer, (uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_read_idx], AMBE_AUDIO_LENGTH + LC_DATA_LENGTH);
|
|
hrc.txDataType = hotspotTxDataType[wavbuffer_read_idx];
|
|
|
|
wavbuffer_read_idx = ((wavbuffer_read_idx + 1) % HOTSPOT_BUFFER_COUNT);""", 1)
|
|
|
|
# ---- 3. TX_2: send a packet data burst instead of pushing the payload through the vocoder
|
|
a = """ if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
if (hrc.txSequence == 0)
|
|
{
|
|
SPI0WritePageRegByteArray(0x02, 0x00, (uint8_t*)deferredUpdateBuffer, LC_DATA_LENGTH); // put LC into hardware
|
|
}
|
|
"""
|
|
assert a in s, "TX_2 hotspot branch missing"
|
|
s = s.replace(a, """ if (settingsUsbMode == USB_MODE_HOTSPOT)
|
|
{
|
|
if (hrc.txDataType != HOTSPOT_TX_TYPE_VOICE)
|
|
{
|
|
// Packet data burst (SMS etc). The 96 bit payload goes into the same
|
|
// register page the radio already uses for LC data, and reg 0x50 below
|
|
// tells the HR-C6000 which kind of data burst to build around it.
|
|
// The vocoder path is left idle - this is what stock firmware lacked.
|
|
SPI0WritePageRegByteArray(0x02, 0x00, (uint8_t*)deferredUpdateBuffer, LC_DATA_LENGTH);
|
|
SPI1WritePageRegByteArray(0x03, 0x00, SILENCE_AUDIO, AMBE_AUDIO_LENGTH);
|
|
hrc.hotspotDMRTxFrameBufferEmpty = true;
|
|
|
|
SPI0WritePageRegByte(0x04, 0x41, 0x80); // Transmit during next Timeslot
|
|
SPI0WritePageRegByte(0x04, 0x50, (hrc.txDataType << 4)); // Data Type = DT_*, Data sync, LCSS = 0
|
|
hrc.txDataType = HOTSPOT_TX_TYPE_VOICE; // consumed
|
|
|
|
slotState = DMR_STATE_TX_1;
|
|
break;
|
|
}
|
|
|
|
if (hrc.txSequence == 0)
|
|
{
|
|
SPI0WritePageRegByteArray(0x02, 0x00, (uint8_t*)deferredUpdateBuffer, LC_DATA_LENGTH); // put LC into hardware
|
|
}
|
|
""", 1)
|
|
|
|
open(p, "w").write(s)
|
|
print("HR-C6000.c patched")
|
|
PYEOF' 2>&1 | tail -5
|
|
```
|
|
|
|
## Правка 4 (строка транскрипта 1052)
|
|
|
|
```bash
|
|
export SSHPASS='Adelina@2015'
|
|
sshpass -e ssh -o StrictHostKeyChecking=no viktor@10.0.0.200 'python3 - <<"PYEOF"
|
|
W="/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
|
p = W + "/source/functions/hotspot.c"
|
|
s = open(p).read()
|
|
if "hotspotTxDataPending" in s:
|
|
raise SystemExit("already patched")
|
|
|
|
# flag: there is at least one packet data burst waiting to go out
|
|
a = "volatile uint8_t hotspotTxDataType[HOTSPOT_BUFFER_COUNT];\n"
|
|
assert a in s
|
|
s = s.replace(a, a + "static volatile bool hotspotTxDataPending = false;// a packet data burst is queued\n", 1)
|
|
|
|
# set it when queuing a data burst
|
|
a = "\t\thotspotTxDataType[wavbuffer_write_idx] = dataType;\n"
|
|
assert a in s
|
|
s = s.replace(a, a + "\t\thotspotTxDataPending = true;\n", 1)
|
|
|
|
# Data bursts are only a few frames long, so the audio pre-buffering threshold
|
|
# (5 frames) would never be reached and the transmission would time out instead.
|
|
a = "\t\t\t\tif (wavbuffer_count > TX_BUFFER_MIN_BEFORE_TRANSMISSION)\n"
|
|
assert a in s, "buffering condition not found"
|
|
s = s.replace(a, "\t\t\t\tif ((wavbuffer_count > TX_BUFFER_MIN_BEFORE_TRANSMISSION) || hotspotTxDataPending)\n", 1)
|
|
|
|
# clear the flag once the buffer has drained
|
|
a = """ case HOTSPOT_STATE_TX_SHUTDOWN:
|
|
"""
|
|
assert a in s, "TX_SHUTDOWN case not found"
|
|
s = s.replace(a, """ case HOTSPOT_STATE_TX_SHUTDOWN:
|
|
if (wavbuffer_count == 0)
|
|
{
|
|
hotspotTxDataPending = false;
|
|
}
|
|
""", 1)
|
|
|
|
# and on (re)initialisation
|
|
a = """ case HOTSPOT_STATE_INITIALISE:
|
|
"""
|
|
assert a in s, "INITIALISE case not found"
|
|
s = s.replace(a, """ case HOTSPOT_STATE_INITIALISE:
|
|
hotspotTxDataPending = false;
|
|
""", 1)
|
|
|
|
open(p, "w").write(s)
|
|
print("patched: data bursts start transmitting immediately")
|
|
PYEOF
|
|
cd ~/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware
|
|
make -f Makefile.opengd77 -j$(nproc) 2>&1 | grep -iE " error |error:" | head -10
|
|
ls -l build_ogd77/OpenMDUV380.bin && arm-none-eabi-size build_ogd77/OpenMDUV380.elf | tail -1' 2>&1 | tail -8
|
|
```
|
|
|
|
## Правка 5 (строка транскрипта 1069)
|
|
|
|
```bash
|
|
export SSHPASS='Adelina@2015'
|
|
sshpass -e ssh -o StrictHostKeyChecking=no viktor@10.0.0.200 'python3 - <<"PYEOF"
|
|
W="/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
|
p = W + "/source/functions/hotspot.c"
|
|
s = open(p).read()
|
|
if "hotspotTxDataPending" in s:
|
|
raise SystemExit("already patched")
|
|
|
|
a = "volatile uint8_t hotspotTxDataType[HOTSPOT_BUFFER_COUNT];\n"
|
|
assert a in s, "array decl"
|
|
s = s.replace(a, a + "static volatile bool hotspotTxDataPending = false;// a packet data burst is queued\n", 1)
|
|
|
|
a = "\thotspotTxDataType[wavbuffer_write_idx] = dataType;\n"
|
|
assert a in s, "queue line"
|
|
s = s.replace(a, a + "\thotspotTxDataPending = true;\n", 1)
|
|
|
|
# Data bursts are only a few frames long, so the audio pre-buffering threshold
|
|
# would never be reached and the transmission would time out instead.
|
|
a = "\t\t\t\tif (wavbuffer_count > TX_BUFFER_MIN_BEFORE_TRANSMISSION)\n"
|
|
assert a in s, "buffering condition"
|
|
s = s.replace(a, "\t\t\t\tif ((wavbuffer_count > TX_BUFFER_MIN_BEFORE_TRANSMISSION) || hotspotTxDataPending)\n", 1)
|
|
|
|
a = "\t\tcase HOTSPOT_STATE_TX_SHUTDOWN:\n"
|
|
assert a in s, "TX_SHUTDOWN"
|
|
s = s.replace(a, a + "\t\t\tif (wavbuffer_count == 0)\n\t\t\t{\n\t\t\t\thotspotTxDataPending = false;\n\t\t\t}\n", 1)
|
|
|
|
a = "\t\tcase HOTSPOT_STATE_INITIALISE:\n"
|
|
assert a in s, "INITIALISE"
|
|
s = s.replace(a, a + "\t\t\thotspotTxDataPending = false;\n", 1)
|
|
|
|
open(p, "w").write(s)
|
|
print("patched OK")
|
|
PYEOF
|
|
cd ~/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware
|
|
make -f Makefile.opengd77 -j$(nproc) 2>&1 | grep -iE " error |error:" | head -10
|
|
echo "=== build result ==="
|
|
ls -l build_ogd77/OpenMDUV380.bin && arm-none-eabi-size build_ogd77/OpenMDUV380.elf | tail -1' 2>&1 | tail -8
|
|
```
|
|
|
|
## Правка 6 (строка транскрипта 1205)
|
|
|
|
```bash
|
|
export SSHPASS='Adelina@2015'
|
|
sshpass -e ssh -o StrictHostKeyChecking=no viktor@10.0.0.200 'python3 - <<"PYEOF"
|
|
W="/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
|
|
|
# --- 1. new RX event type -------------------------------------------------
|
|
p = W + "/include/functions/hotspot.h"
|
|
s = open(p).read()
|
|
if "HOTSPOT_RX_DATA_FRAME" not in s:
|
|
a = "\tHOTSPOT_RX_STOP,\n"
|
|
assert a in s
|
|
s = s.replace(a, a + "\tHOTSPOT_RX_DATA_FRAME,// a packet data burst (SMS etc) was received off air\n", 1)
|
|
open(p,"w").write(s)
|
|
print("hotspot.h: RX event added")
|
|
|
|
# --- 2. bring the slot type encoder over from the MK22 branch -------------
|
|
src = open("/home/viktor/OpenGD77/firmware/source/hotspot/DMRSlotType.c").read()
|
|
start = src.index("static const uint16_t ENCODING_TABLE_2087[] =")
|
|
end = src.index(";", src.index("};", start))
|
|
table = src[start:src.index("};", start)+2]
|
|
|
|
p = W + "/source/functions/hotspot.c"
|
|
s = open(p).read()
|
|
if "ENCODING_TABLE_2087" in s:
|
|
raise SystemExit("hotspot.c already has the slot type encoder")
|
|
|
|
anchor = "static void BPTCencode(const uint8_t *inputData, uint8_t *outputData)\n{"
|
|
assert anchor in s, "BPTCencode definition not found"
|
|
|
|
block = """// Slot Type encoder, needed to build received data bursts for the host.
|
|
// Golay(20,8) table and logic taken from the project\\'s own DMRSlotType implementation.
|
|
""" + table + """
|
|
|
|
// Writes colour code + data type (with FEC) into the Slot Type half-bursts of a frame.
|
|
static void DMRSlotTypeEncode(uint32_t colorCode, uint32_t dataType, uint8_t *frame)
|
|
{
|
|
uint8_t slotType[3];
|
|
|
|
slotType[0] = (colorCode << 4) & 0xF0U;
|
|
slotType[0] |= (dataType << 0) & 0x0FU;
|
|
|
|
uint16_t cksum = ENCODING_TABLE_2087[slotType[0]];
|
|
|
|
slotType[1] = (cksum >> 0) & 0xFFU;
|
|
slotType[2] = (cksum >> 8) & 0xFFU;
|
|
|
|
frame[12] = (frame[12] & 0xC0U) | ((slotType[0] >> 2) & 0x3FU);
|
|
frame[13] = (frame[13] & 0x0FU) | ((slotType[0] << 6) & 0xC0U) | ((slotType[1] >> 2) & 0x30U);
|
|
frame[19] = (frame[19] & 0xF0U) | ((slotType[1] >> 2) & 0x0FU);
|
|
frame[20] = (frame[20] & 0x03U) | ((slotType[1] << 6) & 0xC0U) | ((slotType[2] >> 2) & 0x3CU);
|
|
}
|
|
|
|
""" + anchor
|
|
|
|
s = s.replace(anchor, block, 1)
|
|
open(p,"w").write(s)
|
|
print("hotspot.c: slot type encoder added (%d bytes of table)" % len(table))
|
|
PYEOF' 2>&1 | tail -6
|
|
```
|
|
|
|
## Правка 7 (строка транскрипта 3341)
|
|
|
|
```bash
|
|
export SSHPASS='Adelina@2015'
|
|
sshpass -e ssh -o StrictHostKeyChecking=no viktor@10.0.0.200 'python3 - <<"PYEOF"
|
|
import glob, os
|
|
W = "/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
|
|
|
# 1. new field at the end of the strings table
|
|
p = W + "/include/user_interface/languages/uiLanguage.h"
|
|
s = open(p).read()
|
|
if "messages[LANGUAGE_TEXTS_LENGTH]" in s:
|
|
print("uiLanguage.h: field already present")
|
|
else:
|
|
s = s.replace(" const char mute[LANGUAGE_TEXTS_LENGTH];\n} stringsTable_t;",
|
|
" const char mute[LANGUAGE_TEXTS_LENGTH];\n"
|
|
" const char messages[LANGUAGE_TEXTS_LENGTH];\n} stringsTable_t;", 1)
|
|
# the struct changed, so the language file version has to move on
|
|
s = s.replace("#define LANGUAGE_TAG_VERSION { 0x00, 0x00, 0x00, 0x05 }",
|
|
"#define LANGUAGE_TAG_VERSION { 0x00, 0x00, 0x00, 0x06 }", 1)
|
|
open(p, "w").write(s)
|
|
print("uiLanguage.h: added .messages, bumped tag version to 6")
|
|
|
|
# 2. give every language a value; designated initialisers mean a missing one would
|
|
# simply render as an empty menu entry, which looks broken.
|
|
done = 0
|
|
for f in sorted(glob.glob(W + "/include/user_interface/languages/*.h")):
|
|
if f.endswith("uiLanguage.h"):
|
|
continue
|
|
t = open(f, "r", encoding="latin-1").read()
|
|
if ".messages" in t:
|
|
continue
|
|
if ".mute" not in t:
|
|
print(" skipped %s (no .mute anchor)" % os.path.basename(f))
|
|
continue
|
|
idx = t.index(".mute")
|
|
eol = t.index("\n", idx)
|
|
t = t[:eol + 1] + ".messages\t\t\t\t= \"Messages\", // MaxLen: 16\n" + t[eol + 1:]
|
|
open(f, "w", encoding="latin-1").write(t)
|
|
done += 1
|
|
print("added .messages to %d language files" % done)
|
|
PYEOF' 2>&1 | tail -6
|
|
```
|
|
|
|
## Правка 8 (строка транскрипта 3357)
|
|
|
|
```bash
|
|
export SSHPASS='Adelina@2015'
|
|
sshpass -e scp -o StrictHostKeyChecking=no /tmp/claude-1000/-home-viktor-claude-uv390/44e0d3e2-595b-4c07-94e1-73e0e3f7531a/menuMessages.c viktor@10.0.0.200:'~/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application/source/user_interface/menuMessages.c' >/dev/null 2>&1
|
|
sshpass -e ssh -o StrictHostKeyChecking=no viktor@10.0.0.200 'python3 - <<"PYEOF"
|
|
W = "/home/viktor/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application"
|
|
|
|
# --- declare the three screens and their menu ids -------------------------
|
|
p = W + "/include/user_interface/menuSystem.h"
|
|
s = open(p).read()
|
|
if "MENU_MESSAGES" not in s:
|
|
s = s.replace("\tMENU_LAST_HEARD,\n",
|
|
"\tMENU_LAST_HEARD,\n\tMENU_MESSAGES,\n\tMENU_MESSAGES_FOLDER,\n\tMENU_MESSAGES_LIST,\n", 1)
|
|
s = s.replace("menuStatus_t uiMessageBox(uiEvent_t *event, bool isFirstRun);",
|
|
"menuStatus_t uiMessageBox(uiEvent_t *event, bool isFirstRun);\n"
|
|
"menuStatus_t menuMessages(uiEvent_t *ev, bool isFirstRun);\n"
|
|
"menuStatus_t menuMessagesFolder(uiEvent_t *ev, bool isFirstRun);\n"
|
|
"menuStatus_t menuMessagesList(uiEvent_t *ev, bool isFirstRun);", 1)
|
|
open(p, "w").write(s)
|
|
print("menuSystem.h: ids and prototypes added")
|
|
else:
|
|
print("menuSystem.h: already declared")
|
|
|
|
# --- register the screens in the dispatch table ---------------------------
|
|
p = W + "/source/user_interface/menuSystem.c"
|
|
s = open(p).read()
|
|
if "menuMessages," in s:
|
|
print("menuSystem.c: already registered")
|
|
else:
|
|
# the table order must match the enum order: our ids come right after MENU_LAST_HEARD
|
|
idx = s.index("static menuFunctionData_t menuFunctions[] =")
|
|
marker = "{ menuLastHeard,"
|
|
j = s.index(marker, idx)
|
|
eol = s.index("\n", j) + 1
|
|
s = s[:eol] + ("\t\t{ menuMessages, NULL, NULL, 0 },\n"
|
|
"\t\t{ menuMessagesFolder, NULL, NULL, 0 },\n"
|
|
"\t\t{ menuMessagesList, NULL, NULL, 0 },\n") + s[eol:]
|
|
|
|
# and a Messages entry in the main menu, using the new language string
|
|
s = s.replace("\t{ 7, MENU_LAST_HEARD },\n",
|
|
"\t{ 7, MENU_LAST_HEARD },\n\t{ 281, MENU_MESSAGES },\n", 1)
|
|
open(p, "w").write(s)
|
|
print("menuSystem.c: screens registered and main menu entry added")
|
|
PYEOF
|
|
cd ~/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware
|
|
make -f Makefile.opengd77 -j$(nproc) 2>&1 | grep -iE "error|undefined" | head -10
|
|
arm-none-eabi-size build_ogd77/OpenMDUV380.elf 2>/dev/null | tail -1' 2>&1 | tail -12
|
|
```
|
|
|
|
## Правка 9 (строка транскрипта 3364)
|
|
|
|
```bash
|
|
export SSHPASS='Adelina@2015'
|
|
sshpass -e ssh -o StrictHostKeyChecking=no viktor@10.0.0.200 '
|
|
W=~/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware/application
|
|
sed -i "s/THEME_ITEM_FG_MENU_ITEM_SELECTED/THEME_ITEM_COLOUR_NONE/g" $W/source/user_interface/menuMessages.c
|
|
sed -i "s/, (strlen(buf) - 1), THEME_ITEM_FG_MENU_ITEM,/, 0, THEME_ITEM_FG_MENU_ITEM,/g" $W/source/user_interface/menuMessages.c
|
|
cd ~/ogd77rel/uv_src/OpenGD77_MDUV380_DM1701_20260130/MDUV380_firmware
|
|
make -f Makefile.opengd77 -j$(nproc) 2>&1 | grep -iE "error|undefined" | head -10
|
|
arm-none-eabi-size build_ogd77/OpenMDUV380.elf 2>/dev/null | tail -1' 2>&1 | tail -10
|
|
```
|
|
|