# Modbus Retention and Private Functions Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Replace the communication-history extension with `0x42` extended-register write and `0x43` odd-count register read, while retaining RTC persistence and documenting the TouchWin functions. **Architecture:** `modbus.c` remains the protocol-only layer: it validates request frames, reads or writes the configured `0x00010000` storage, and builds RTU responses. `main.c` owns persistence policy, adding `0x42` to successful write operations so the existing RTC backup service saves it. The host protocol harness executes the parser against explicit backing storage; the TouchWin document contains the matching HMI C functions and PSW bindings. **Tech Stack:** STM32F407 C firmware, uC/OS-II, IAR project, Modbus RTU, RTC backup registers, GNU GCC host test executable with `assert`. ## Global Constraints - Treat `0x41`, `0x42`, and `0x43` as this training project's private protocol, not standard Modbus function codes. - Keep standard data address range `0x0000..0x270F`; only private `0x41` and `0x42` access `0x00010000`. - `0x42` writes one 16-bit value to `0x00010000`, has a fixed 10-byte RTU ADU, and echoes its complete request PDU on success. - `0x43` reads one to 123 contiguous holding registers and rejects even quantities and ranges beyond `0x270F`. - Remove all communication-history state and API, including RAM records, `0x43` history parsing, HMI PSW `500..509`, and history display text. - Keep TouchWin clear local: it writes only `PSW350 = 0` and does not transmit or modify PLC data. - Follow the supplied Xinje C conventions: four-space indentation, braces on separate lines, explicit validation, named constants, and concise Doxygen-style API comments. - Preserve all unrelated uncommitted user changes. Do not alter the pre-existing `.git/index.lock`. --- ## File Structure - `Core/Modbus/modbus.h`: private function-code constants and the minimal `MODBUS_SLAVE` data model. - `Core/Modbus/modbus.c`: private `0x42` write builder, `0x43` odd-count read builder, request dispatch, and removal of history code. - `Core/Modbus/modbus_test.c`: host-only parser regression harness using caller-owned holding-register and coil storage. - `Core/Src/main.c`: persistence classification for `0x42` and removal of history collection from the application task. - `document/TouchWin自组Modbus功能码函数.md`: TouchWin C definitions and callable functions for read, write, clear, and odd-count read. - `document/TouchWin_Modbus_附加功能测试界面方案.svg`: revised training screen reference without communication history, using `PSW350`, `PSW351`, and `PSW370..PSW494`. ### Task 1: Add Failing Protocol Tests for the Remapped Extensions **Files:** - Create: `Core/Modbus/modbus_test.c` - Modify: none - Test: `Core/Modbus/modbus_test.c` **Interfaces:** - Consumes: `ModbusSlaveInit()`, `ModbusConfigureExtendedHoldingRegister()`, `ModbusProcessFrame()`, `ModbusCrc16()` from `modbus.h`. - Produces: `modbus_protocol_test.exe`, a host parser test binary that returns `0` only when every `assert` succeeds. - [ ] **Step 1: Restore a host test harness with explicit backing storage** Create `Core/Modbus/modbus_test.c` with project-compatible initialization. The test must not call the obsolete two-argument `ModbusSlaveInit()` from `HEAD`; it must supply real storage: ```c static uint16_t TestHoldingRegisters[MODBUS_DATA_POINT_COUNT]; static uint8_t TestCoilStorage[MODBUS_COIL_STORAGE_SIZE]; static uint16_t TestExtendedRegister; static void TestInitSlave(MODBUS_SLAVE *slave) { assert(ModbusSlaveInit(slave, TEST_SLAVE_ADDRESS, TestHoldingRegisters, TestCoilStorage) == MODBUS_STATUS_OK); assert(ModbusConfigureExtendedHoldingRegister(slave, &TestExtendedRegister) == MODBUS_STATUS_OK); } ``` - [ ] **Step 2: Write the failing `0x42` write test** Add `TestWriteExtendedRegister()` using a 10-byte request. It must send `01 42 00 01 00 00 BE EF CRC_L CRC_H`, expect `MODBUS_STATUS_OK`, a 10-byte response that echoes bytes `0..7`, a valid CRC, and `TestExtendedRegister == 0xBEEFU`. ```c static void TestWriteExtendedRegister(void) { uint8_t request[10U] = {1U, 0x42U, 0U, 1U, 0U, 0U, 0xBEU, 0xEFU, 0U, 0U}; uint8_t response[MODBUS_RTU_ADU_MAX_LENGTH]; uint16_t responseLength; MODBUS_SLAVE slave; TestInitSlave(&slave); TestAppendCrc(request, 8U); assert(ModbusProcessFrame(&slave, request, sizeof(request), response, sizeof(response), &responseLength) == MODBUS_STATUS_OK); assert(responseLength == sizeof(request)); assert(memcmp(response, request, 8U) == 0); assert(TestExtendedRegister == 0xBEEFU); TestAssertFrameCrc(response, responseLength); } ``` Add `` for `memcmp`. Add a sibling test whose address bytes encode `0x00010001`; it must expect `MODBUS_STATUS_EXCEPTION`, function byte `0xC2`, and exception code `MODBUS_EXCEPTION_ILLEGAL_DATA_ADDR`. - [ ] **Step 3: Write the failing `0x43` odd-count read tests** Seed `TestHoldingRegisters[0]`, `[1]`, and `[2]` with `0x1234`, `0xABCD`, and `0x0001`. Send `01 43 00 00 00 03 CRC_L CRC_H`; assert status OK, response payload `01 43 06 12 34 AB CD 00 01`, and a valid CRC. Add a separate request with count `2` and assert exception function `0xC3` with code `MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE`. - [ ] **Step 4: Run tests to verify RED** Run: ```powershell gcc -std=c99 -Wall -Wextra -Werror -ICore/Modbus Core/Modbus/modbus.c Core/Modbus/modbus_test.c -o modbus_protocol_test.exe .\modbus_protocol_test.exe ``` Expected: the executable reaches an assertion because current `0x42` is an odd-count read and current `0x43` is communication history. The source must compile cleanly; a test compile error is not an acceptable RED result. - [ ] **Step 5: Commit the test-only change when the repository lock is released** ```powershell git add -- Core/Modbus/modbus_test.c git commit -m "test: cover remapped private modbus functions" ``` Do not run these commands while `.git/index.lock` exists. ### Task 2: Remap the Parser and Remove Communication History **Files:** - Modify: `Core/Modbus/modbus.h:45-132` - Modify: `Core/Modbus/modbus.c:56-85`, `Core/Modbus/modbus.c:530-755`, `Core/Modbus/modbus.c:785-890`, `Core/Modbus/modbus.c:990-1100` - Test: `Core/Modbus/modbus_test.c` **Interfaces:** - Consumes: the constants and `extendedHoldingRegister` supplied by `MODBUS_SLAVE`. - Produces: `MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS (0x42U)`, `MODBUS_FC_READ_ODD_COUNT_REGS (0x43U)`, and successful protocol dispatch for both requests. - [ ] **Step 1: Change only the public protocol model** In `modbus.h`, replace the current private constants with: ```c #define MODBUS_FC_READ_EXTENDED_HOLDING_REGS (0x41U) #define MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS (0x42U) #define MODBUS_FC_READ_ODD_COUNT_REGS (0x43U) #define MODBUS_EXTENDED_HOLDING_ADDRESS (0x00010000UL) ``` Delete `MODBUS_FC_READ_COMMUNICATION_HISTORY`, every `MODBUS_HISTORY_*` macro, `MODBUS_HISTORY_RECORD`, all history members of `MODBUS_SLAVE`, and the `ModbusRecordHistory()` declaration. Keep the extended-register pointer and its configuration API. - [ ] **Step 2: Implement the minimal `0x42` write response builder** Replace the old `ModbusBuildReadOddCountRegistersResponse()` at the `0x42` position with a builder accepting a 32-bit address and one 16-bit value. It must validate `ModbusIsExtendedRegisterAddressValid()`, write through `slave->extendedHoldingRegister`, and build the fixed response: ```c txBuffer[MODBUS_RTU_ADDRESS_INDEX] = slave->slaveAddress; txBuffer[MODBUS_RTU_FUNCTION_INDEX] = MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS; ModbusWriteU32(&txBuffer[2U], startAddress); ModbusWriteU16(&txBuffer[6U], registerValue); *txLength = 8U; ``` Add `ModbusWriteU32()` beside `ModbusReadU32()` and then append CRC. Require `txCapacity >= 10U`. Return illegal address for any address except `0x00010000`. - [ ] **Step 3: Move odd-count read to `0x43`** Retain the existing odd-count validation and response data loop, but rename its comment to `0x43` and assign `MODBUS_FC_READ_ODD_COUNT_REGS` to the response function byte. Its PDU remains 16-bit start address plus 16-bit count; it must not retain any offset/count history interpretation. - [ ] **Step 4: Update frame dispatch and delete the complete history implementation** In `ModbusProcessFrame()`, use exact request lengths and payload parsing: ```c case MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS: if (rxLength != 10U) { requestResult = MODBUS_REQUEST_ILLEGAL_VALUE; break; } requestResult = ModbusBuildWriteExtendedRegisterResponse( slave, ModbusReadU32(&rxBuffer[2U]), ModbusReadU16(&rxBuffer[6U]), txBuffer, txCapacity, txLength); break; case MODBUS_FC_READ_ODD_COUNT_REGS: if (rxLength != MODBUS_RTU_READ_REQUEST_LENGTH) { requestResult = MODBUS_REQUEST_ILLEGAL_VALUE; break; } requestResult = ModbusBuildReadOddCountRegistersResponse( slave, ModbusReadU16(&rxBuffer[2U]), ModbusReadU16(&rxBuffer[4U]), txBuffer, txCapacity, txLength); break; ``` Delete `ModbusBuildHistoryResponse()`, the history initialization loop in `ModbusSlaveInit()`, and the full `ModbusRecordHistory()` definition. Remove variables that become unused. - [ ] **Step 5: Run the host test to verify GREEN** Run the same two commands from Task 1. Expected: `modbus_protocol_test.exe` exits `0`, with no GCC warnings. Add a `0x41` regression assertion in the test harness before accepting the result, proving its 10-byte request still returns the configured 16-bit value. - [ ] **Step 6: Commit parser and tests when the repository lock is released** ```powershell git add -- Core/Modbus/modbus.h Core/Modbus/modbus.c Core/Modbus/modbus_test.c git commit -m "feat: remap private modbus extensions" ``` ### Task 3: Persist `0x42` Writes and Remove Application History Calls **Files:** - Modify: `Core/Src/main.c:107-109`, `Core/Src/main.c:488-536`, `Core/Src/main.c:739-840` - Test: `Core/Modbus/modbus_test.c` **Interfaces:** - Consumes: `MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS` and existing `ModbusBackupSave()`. - Produces: an application-level successful `0x42` transaction that is classified as a write and therefore persisted by the existing RTC path. - [ ] **Step 1: Remove history-only declarations and helpers** Delete the `AppGetHistoryResultCode()` prototype and definition, the `historyResult` local variable, and each `ModbusRecordHistory()` call. Preserve existing request validation, link-state accounting, debug snapshots, response transmission, and protocol-error counting. - [ ] **Step 2: Add `0x42` to write classification** Update `AppIsModbusWriteFunction()` with the private write code: ```c case MODBUS_FC_WRITE_EXTENDED_HOLDING_REGS: return 1U; ``` It belongs in the same `switch` as `0x05`, `0x06`, `0x0F`, and `0x10`, so the existing success-only block invokes `ModbusBackupSave(&ModbusSlave)` for a valid `0x42` write. Do not call the backup service from a failed or exception response. - [ ] **Step 3: Build the embedded project** Open `EWARM/Modbus.ewp` in IAR Embedded Workbench and build the `Modbus` target. Expected: zero C errors from removed history symbols and the new `0x42` constant. The current environment does not provide `iccarm`, so record an unavailable IAR build rather than substituting a host build for firmware validation. - [ ] **Step 4: Run a controlled hardware persistence check** With machine outputs isolated and VBAT fitted, write a nonzero value through `0x42`, verify the `0x42` echo, power-cycle the test PLC, then use `0x41` to read back the same value. Clear only `PSW350` before the power cycle; do not send a PLC write as part of clearing. Record the write and read frames with the observed value. - [ ] **Step 5: Commit the application change when the repository lock is released** ```powershell git add -- Core/Src/main.c git commit -m "feat: persist extended register writes" ``` ### Task 4: Publish the TouchWin Functions and Remove History from the Screen Reference **Files:** - Modify: `document/TouchWin自组Modbus功能码函数.md` - Modify: `document/TouchWin_Modbus_附加功能测试界面方案.svg` - Test: `document/TouchWin自组Modbus功能码函数.md` **Interfaces:** - Consumes: the final `0x41/0x42/0x43` wire contracts and the shared TouchWin helpers `ModbusAppendCrc()`, `ModbusSendReceive()`, and `ModbusCheckResponse()`. - Produces: copyable TouchWin C functions plus a reference screen that has no communication-history controls. - [ ] **Step 1: Replace the private-function and PSW mapping tables** Document only these mappings: ```c #define MODBUS_FUNCTION_READ_EXTENDED_REGISTER (0x41) #define MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER (0x42) #define MODBUS_FUNCTION_READ_ODD_COUNT_REGS (0x43) #define PSW_EXTENDED_RESULT (350) #define PSW_EXTENDED_WRITE_VALUE (351) #define PSW_ODD_START_ADDRESS (370) #define PSW_ODD_COUNT (371) #define PSW_ODD_RESULTS (372) ``` Delete all history constants, `PSW500..PSW509`, and the history section. State that `PSW372..PSW494` contains 123 WORD results, requiring 123 display components to show all values; a training screen may show the first eight or 16 only. - [ ] **Step 2: Add the `0x42` TouchWin write function** Add a complete function that reads `PSW351`, sends the fixed frame, verifies the echoed eight-byte PDU, and always updates `PSW319` and `PSW320`: ```c writeValue = PSW[PSW_EXTENDED_WRITE_VALUE]; request[0] = MODBUS_SLAVE_ADDRESS; request[1] = MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER; request[2] = 0x00; request[3] = 0x01; request[4] = 0x00; request[5] = 0x00; request[6] = (BYTE)(writeValue >> 8); request[7] = (BYTE)writeValue; ModbusAppendCrc(request, 8); ``` The response capacity and required response length are both 10 bytes. When status is OK, compare `response[2]..response[7]` with `request[2]..request[7]`; on mismatch set `MODBUS_STATUS_RESPONSE_ERROR`. - [ ] **Step 3: Add the local clear function and move odd-count read to `0x43`** Document the clear function exactly as: ```c void ModbusClearExtendedResult(void) { PSW[PSW_EXTENDED_RESULT] = 0; } ``` It must not call `ModbusSendReceive()`. Change the odd-count example's function constant to `0x43` and retain its existing `PSW370`, `PSW371`, `PSW372..PSW494`, count, range, and byte-count validation. - [ ] **Step 4: Revise the SVG reference screen** Replace the history panel with the `0x42` write value (`PSW351`), Write button, `PSW350` read result, and Clear button. Change the right-hand read panel heading from `42` to `43`; keep its eight visible sample result boxes and state that the full backing range has 123 WORDs. Change the RTC trigger text to `05 / 06 / 0F / 10 / 42 成功后`; remove all history and PSW500-series text. - [ ] **Step 5: Verify documentation and commit when the repository lock is released** Run: ```powershell Select-String -Path 'document\TouchWin自组Modbus功能码函数.md','document\TouchWin_Modbus_附加功能测试界面方案.svg' -Pattern '通信历史|PSW500|PSW501|PSW502|MODBUS_FUNCTION_READ_HISTORY' ``` Expected: no matches. Then inspect the SVG at normal size to verify the `0x42` write and Clear controls do not overlap text. ```powershell git add -- document/TouchWin自组Modbus功能码函数.md document/TouchWin_Modbus_附加功能测试界面方案.svg git commit -m "docs: document retained modbus extensions" ``` ## Final Verification - [ ] Compile and run `modbus_protocol_test.exe` with GCC; it exits `0` without warnings. - [ ] Build `EWARM/Modbus.ewp` in IAR and resolve all project compile errors. - [ ] Confirm no production source references `history`, `History`, `MODBUS_HISTORY`, `ModbusRecordHistory`, or `MODBUS_FC_READ_COMMUNICATION_HISTORY`. - [ ] Confirm the documentation has exactly the `0x41` read, `0x42` write, and `0x43` odd-count-read private functions. - [ ] Perform the controlled write, display-clear, power-cycle, and readback test with VBAT fitted.