48 KiB
DA-07 Service Tool — Interface Control Document (ICD)
Scope: DA-07 / DA-07B / DA-07C firmware family (build symbols MODEL_DA07,
+MODEL_DA07B, +MODEL_DA07C). Status: descriptive, not prescriptive — the
DA-07 firmware is EOL; this document records what the shipped firmware does so a
replacement service-tool application can be built against it without access to the
firmware source. Firmware is authoritative.
Primary sources (firmware): service.c (protocol + state machine),
usart.c (transport, framing, serializers), utility.c (hex/time helpers),
config.h/io.h/modbus.h/protocol.h (structures & enums), crc.c (Modbus CRC16).
Line citations are file:line against the repository at D:\Work\DA-07 source code.
Reader's note on byte fidelity. The DA-07 build compiles with
-fpack-struct(PackStructureMembers=TrueinDA-PQ-Xmega.cproj), so all structs are packed with no padding; a struct's wire image is the simple concatenation of its members.charis unsigned (-funsigned-char). The MCU is little-endian.floatis 32-bit IEEE-754, little-endian. These facts are assumed throughout.
1. Transport & PHY
| Property | Value | Evidence |
|---|---|---|
| Physical port | Dedicated service port on a 3.5 mm jack; USB-A↔3.5 mm cable to the tech's PC | hardware; user-confirmed |
| MCU UART | USART2 = USARTE0 (Port E0) | usart.c:16, USART2_Init usart.c:435 |
| Baud rate | 9600 | main.c:205 (USART2_Init(9600, …)) |
| Data bits | 8 (USART_DATA_8BITS) |
main.c:205 |
| Parity | None (USART_PARITY_NONE) |
main.c:205 |
| Stop bits | 1 (USART_STOP_1BIT) |
main.c:205 |
| Flow control | None | usart.c (no RTS/CTS on USARTE0) |
| Direction | Full-duplex async, idle-high (standard UART) | — |
So: 9600 8-N-1, no flow control. This port is electrically and logically separate from the RS-485 Modbus bus to the sensor pods (USART0 = USARTF0, Port F). The service tool never sees the RS-485 wires directly; instead the firmware mirrors Modbus frames onto the service port on request (see §10, I/O capture).
Other UARTs (context, not used by the service tool): USART1=USARTD0 =
NetBurner SBL2e Ethernet module — this is the DA-07's path to the CimScan server
(115200 8-N-1, usart.c:385-413, netburner.c). USART3=USARTC0 is the DNT90 radio
port but is only initialized on DA-33 (USART3_Init is #ifdef MODEL_DA33,
usart.c:480-513); on DA-07 the radio is absent. USART4=USARTD1 secondary LAN.
(usart.c:14-18.)
Scope note: the DA-07 reaches the server over Ethernet (NetBurner), not the DNT90 radio. The radio/
wireless.cis entirely DA-33. The service tool itself only ever uses the local service UART (USART2); there is no remote/server-routed service-tool path on DA-07 (SERVICE_TOOL_CMD_ID(36)has noProcessMsgcase and fails the inbound length gate, so it is unhandled and NAKed).
Special option 0x42 (inert on DA-07 — do not use): setting special option
0x42 (command ~O42) makes ServiceTick early-return (service.c:2430, not
model-guarded) and the service-port RX ISR try to forward bytes to the DNT radio UART
(usart.c:258-263). On DA-07 that radio UART (USARTC0) is never initialized, and
the 115200 re-init at service.c:2329 is #ifdef MODEL_DA33, so 0x42 simply
freezes normal service communication while doing nothing useful. It is a DA-33
diagnostic. A DA-07 tool must not send it.
2. Frame format & integrity
The service protocol is a line-oriented ASCII-hex text protocol, not raw binary. Every frame in both directions has the form:
~ <type> <payload …> <CC> <CR>
│ │ │ │ └─ 0x0D, terminator
│ │ │ └─────── 2 ASCII-hex chars = 8-bit checksum (see below)
│ │ └──────────────────── zero or more fields, ASCII (mostly hex digits)
│ └──────────────────────────── 1 ASCII char = message/command type ('A'..'Z')
└─────────────────────────────── 0x7E '~' = start-of-frame
- Start of frame:
~(0x7E). The receiver resets its buffer on any~(firmware RX:usart.c:267-268). - Type byte: a single ASCII letter selecting the message (§7) or command (§8).
- End of frame:
CR(0x0D). Firmware marks a frame ready only when CR arrives and the buffer started with~(usart.c:272-273). - Max frame length: firmware service RX buffer is
SV_SIZEbytes (serial.h/usart.c); transmit buffersvTxBuflikewise. Keep frames within the buffer (the firmware clamps/truncates rather than overflowing —usart.c:277-278,usart.c:925).
2.1 Checksum
An 8-bit additive checksum, modulo 256, emitted as 2 uppercase ASCII-hex digits immediately before the CR.
- Outbound (firmware→tool),
SendSvcCheckusart.c:1040-1057: sum every non-zero byte already in the frame buffer (this includes the leading~and all ASCII-hex payload bytes), takesum & 0xFF, append as 2 hex chars, then append CR. - Inbound (tool→firmware),
SvcChecksumOKservice.c:1917-1927: sum bytessvRxBuf[0 .. svRxTail)(i.e. from~up to but not including the checksum), take& 0xFF, compare to the 2 hex chars at the checksum position.
The tool must compute its outgoing checksum exactly as the firmware verifies it: sum of all bytes from
~through the last payload byte, mod 256, 2 hex digits. (The firmware's own outgoing routine skips embedded zero bytes, but a well-formed frame has no embedded zeros before the checksum, so the two definitions agree in practice. See BUG-ICD-07.)
2.2 Escaping
The only reserved byte is ~ (0x7E). When building a frame, any payload byte equal
to ~ is replaced by a space ' ' (0x20): outbound AddToSvBuf usart.c:920-923
and SendSvcChar usart.c:970-973. Because the payload is otherwise ASCII-hex
(digits 0-9A-F) and tab delimiters, ~ can only occur inside free-text string
fields (e.g. a station name); such a ~ is lossily converted to a space. The tool
should likewise avoid sending literal ~ inside string fields.
2.3 Field delimiter
A TAB (0x09) separates the human-readable label from the value in station-setting
messages (the label text is stored with a trailing \t, service.c:396-435). Most
other messages are fixed-position concatenated hex with no delimiters.
3. Data-type encodings
All multi-byte values are serialized to ASCII-hex by PrintHex
(utility.c:65-76), which walks the value in memory order (low address first).
Because the MCU is little-endian, multi-byte integers and floats are emitted
least-significant-byte first, two hex chars per byte. Example: uint16_t 0x1234
→ memory 34 12 → text "3412".
Contrast: the RS-485/Modbus path uses
PrintHexRev(big-endian hex). The service path usesPrintHex(little-endian hex) forSendSvcInt/Long/Float(usart.c:994-1016). Do not assume Modbus byte order on the service port. Exception:PrintTime(utility.c:97-100) emits auint32big-endian viasprintf("%04X%04X", hi, lo)— but the serviceH/F/Gmessages send time viaSendSvcLong(little-endian), so time on the service wire is little-endian unless a specific message says otherwise. Verify per message in §7. (See BUG-ICD-03.)
The firmware exposes these data-type codes to the tool via the type nibble in
station-setting label strings (the B/C record's 4th char, scheme documented at
service.c:38-52). Note this is a different numbering from the internal DTYPE_*
enum in service.h:5-14 (which is 0=UBYTE,1=SBYTE,2=UINT,…,9=HEX and is used only by
the #ifdef DEBUGGING ~I path). The station-setting type codes are:
| Code | Meaning | Wire encoding |
|---|---|---|
| 0 | unsigned byte | 2 hex chars |
| 1 | unsigned int (16-bit) | 4 hex chars, little-endian |
| 2 | signed int (16-bit) | 4 hex chars, little-endian, two's-complement |
| 3 | unsigned long (32-bit) | 8 hex chars, little-endian |
| 4 | signed long (32-bit) | 8 hex chars, little-endian, two's-complement |
| 5 | float (32-bit IEEE-754) | 8 hex chars, little-endian |
| 6 | char string | raw ASCII bytes (2 hex chars each when sent via SendSvcHex; or literal chars via SendSvcString) |
| 7 | IP address | 4 bytes (8 hex chars), in xpLocalIp order |
| 8 | MAC address | 6 bytes (12 hex chars) |
| 9 | version | high.low bytes |
| A | 4-byte serial number (MAC-like) / rssi — see note | — |
| B | baud rate | 16-bit, little-endian |
| C | rssi (from radio) | byte |
Doc inconsistency in firmware: the two comment tables in
service.cdisagree on codes A/B/C (one says A=serial, B=baud, C=rssiservice.c:50-52; the other says A=rssiservice.c:386-387). Treat the first table (lines 38-52) as canonical for station settings; it matches the actualPrintHexfield sizes used inSendStationSettings. (BUG-ICD-04.)
Important on TIME: timestamps are a 32-bit time_t-style count of seconds since
1970-01-01, sent little-endian on the service wire (SendSvcLong). Treat it as
LOCAL time, not UTC. The firmware's clock-validity constant is
JAN012014 = 1388552400 (config.h:26), which is midnight 2014-01-01 EST (UTC
would be 1388534400 — a 5-hour/18000-second difference), so the device's clock is
maintained in local time, and the VB6 tool renders these timestamps directly as local
HH:MM:SS with no UTC offset applied. The Python tool should do the same: display
the epoch as-is in the unit's local zone; do not convert as UTC or you'll be off by
the local offset. (Caveat: the exact zone is whatever the server/installer set the
clock to; EST is what the validity constant assumes.) Values below JAN012014 are an
unsynced clock = seconds-since-boot, not a real date.
Strings / names: NAME_SIZE = 16 (config.h:90). Station and channel names are
fixed 16-byte fields, space-padded, not necessarily null-terminated on the wire.
Serial numbers are SERIAL_SIZE = 8 bytes (config.h:91).
3.1 Inbound (tool→firmware) value encoding — CRITICAL ASYMMETRY
The firmware does NOT echo-decode the same way it encodes. When the firmware
sends a setting it uses little-endian hex (PrintHex); but when the tool writes a
setting back, the firmware parses the value field with atoi/atof — i.e. plain
DECIMAL ASCII text, not hex. A tool that echoes the received hex back into a write
sends the wrong number (atoi("3C00") = 3, not 0x3C = 60). This is the single most
important thing to get right in the rewrite, and it is per-field:
| Command | Value field encoding on write | Evidence |
|---|---|---|
~B station setting (most) |
decimal (atoi/atof) — e.g. comms timeout, ports, baud, activation energy & pressures (floats), modbus timeout |
service.c:696-697 |
~B idx 1 station name |
raw ASCII bytes (memcpy) |
service.c:724 |
~B idx 4 baseSN ("High 4 bytes of serial") |
hex, per byte (AscToByte), big-endian byte order |
service.c:768-779 |
~B idx 7/10/11 IP addresses |
dotted-decimal text ("192.168.2.18", atoi per octet) — NOT 8 hex chars |
service.c:700-717 |
~C device setting value |
decimal (atoi) |
service.c:1757 |
~C case 5 device serial[3] |
hex, per byte (AscToByte) |
service.c:1807-1809 |
~D channel setting value (limits 2-5, scale 6, offset 7) |
decimal float (atof) — NOT hex floats |
service.c:1575-1576 |
~E alarm-indicator (index/type/data) |
hex (AscToByte) both directions — symmetric |
service.c:1837-1839 |
So the encoding is direction- and field-dependent: read = hex; write = decimal,
except names (ASCII), serials (hex), IPs (dotted-decimal), and the whole ~E
alarm-indicator message (hex). (Logged as BUG ST-13.)
4. Session model & state machine
4.1 Handshake (ACK / NAK / IDLE)
The session is a strict ping-pong: the firmware sends exactly one frame, then waits for the tool to respond before sending the next. The tool's responses:
| Tool frame | Meaning | Firmware reaction |
|---|---|---|
~Z1 + CC + CR |
ACK — got it, send next | RefreshServiceTool() advances and sends the next item (service.c:2390-2398) |
~Z0 + CC + CR |
NAK — resend last | firmware re-sends the previous frame (SendToSvc(), service.c:2391-2394) |
The firmware itself emits:
| Firmware frame | Meaning |
|---|---|
~Z1 |
ACK of a tool command that was processed OK (SendAcknowledgement(ACK), service.c:2415) |
~Z0 |
NAK — bad start char, too-short frame, bad checksum, or unknown type (service.c:2224,2238,2403,2410) |
~Z2 |
IDLE — "nothing to send right now", emitted about once per second when idle (service.c:2504) |
A received frame of any recognized type also counts as an implicit ACK of the
firmware's previous message (service.c:164-166).
4.2 Liveness / timeout
svcActiveis set to 10 whenever a frame arrives from the tool, and decremented once per second when idle (service.c:2441,2489,2501). It is the firmware's "tool is connected" counter.- While in I/O-capture mode (
svcSendIO), if no traffic is seen for >25 seconds the firmware auto-disables capture (service.c:2458-2461). - After 5 consecutive IDLEs with no tool message, the firmware forces
svcState = SVC_POLL(steady-state,service.c:2506-2510).
4.3 Refresh sequence (svcState)
On connect (or on a ~A refresh request) the firmware walks an ordered sequence,
sending one frame per tool-ACK. States (service.h:34-43, driver
RefreshServiceTool service.c:1967-2202):
| Order | State | Frames sent | Builder |
|---|---|---|---|
| 0 | SVC_START |
one ~A config frame (§7.A0) |
SendServiceToolConfig service.c:1267 |
| 1 | SVC_TYPES |
one ~A device-type frame per supported type |
SendDeviceTypes service.c:1284 |
| 2 | SVC_STATION |
one ~B/~C station-setting frame per setting |
SendStationSettings service.c:496 |
| 3 | SVC_INIT |
per device: one ~D device-settings, then one ~E per channel (and ~W per channel on 07C) |
service.c:1999-2064 |
| 4 | SVC_ALARM |
one ~M alarm-group frame per indicator (×16) |
SendAlarmIndSettings service.c:1422 |
| 5 | SVC_AVERAGES |
~F average-values frame for each device flagged D_UPDATE_AVG |
SendDeviceAverages service.c:1093 |
| 6 | SVC_CURRENT |
~G current-values frame for each device flagged D_UPDATE_INPUT |
SendDeviceInputs service.c:1028 |
| 7 | SVC_NAMES |
~P channel-name/CT-serial frames for devices flagged D_UPDATE_NAMES |
SendChanName service.c:1205 |
| 8 | SVC_DEVICE |
~D/~E/~W for devices flagged D_UPDATE_DEVICE |
service.c:2141-2188 |
| 9 | SVC_POLL |
one ~H operational-statistics frame per second |
SendOperationalStatics service.c:1305 |
The states fall through in the source: once a phase's queue empties it advances
and the next phase may begin within the same call. After SVC_POLL the firmware
loops back to SVC_AVERAGES (service.c:2199-2200), so the steady state continually
re-pushes averages → current → names → device → stats as new data is flagged.
A tool that wants a clean full snapshot sends
~A(refresh), then ACKs each frame with~Z1until it observes~Hframes (which signal it has reached steady-stateSVC_POLL).
5. Firmware → tool message reference
Frame = ~ + type + fields + CC + CR. Offsets below are within the payload, in
decoded bytes (each byte = 2 hex chars on the wire) unless noted. All multi-byte
values little-endian (§3).
5.A0 ~A (subtype 0) — Service-tool configuration · SendServiceToolConfig service.c:1267
Sent first in SVC_START. Payload bytes:
| # | Field | Notes |
|---|---|---|
| 0 | 00 |
subtype 0 = "config" (distinguishes from device-type ~A) |
| 1 | MODEL |
7 for DA-07 (config.h:59) |
| 2 | message version | always 01 |
| 3 | MAX_DEVICES |
16 for DA-07 (config.h:60) |
| 4 | MAX_DEVICE_CHANS |
10 (config.h:63) |
| 5 | GetMaxDevTypes() |
number of selectable device types |
| 6 | MAX_INDICATORS |
16 (io.h:304) |
| 7 | MAX_IND_ADDR |
8 (io.h:305) |
5.A1 ~A (device-type) — Device-type descriptor · SendDeviceTypes→SendDeviceInfo service.c:1284
Sent once per type in SVC_TYPES. Payload (confirmed against the legacy VB6
decoder):
| Field | Encoding | Meaning |
|---|---|---|
idx |
byte | device-type index (1-based) |
chans |
byte | number of channels this type has |
genType |
nibble | generic class: 0 analog, 1 particle-counter, 2 pulse, 3 CS-series, 4 din/dout, 5 din, 6 dout, 7 alarm, 8 UniversalModbus (modbus.c:383-397) |
dataDP |
nibble | data type / decimal-places hint |
id |
string + TAB | type name (e.g. "PD-17"), TAB-terminated |
chanNames |
pipe-|-delimited string |
per-channel default names |
(genType/dataDP are packed as two nibbles of one byte. SendDeviceInfo lives in
modbus.c; the PROGMEM descriptor strings are modbus.c:401-457.)
5.B/C ~B/~C — Station setting · SendStationSettings service.c:496
One frame per station setting. The payload is the PROGMEM label record followed by
the value. The label record (service.c:396-435) is:
<editable><line><type><text…>\t where
editable:'B'= user may edit,'C'= display-only.line: 2 hex chars = display row.type: 1 hex char = data type code (§3).text: human label, terminated by TAB (0x09). Then the value is appended as hex per the type. See §6 for the full per-index table.
5.D ~D — Device settings · SendDeviceSettings service.c:1243
~D + device(byte) + D_PARAMS minus the channel array (io.h:179-193).
For the DA-07 build the sent bytes are:
| Off | Size | Field | Notes |
|---|---|---|---|
| 0 | 1 | type |
device type (modbus.h enum); 0/0xFF = unused |
| 1 | 1 | addr |
Modbus address (union with setID) |
| 2 | 1 | delay |
alarm delay (update cycles) |
| 3 | 1 | control |
device control byte (DA-07 only) |
| 4 | 3 | serial[3] |
bytes 4-6 of serial no. |
→ 7 payload bytes (14 hex chars). The chan[10] array is intentionally excluded |
|||
(sizeof(D_PARAMS) - MAX_DEVICE_CHANS, service.c:1251). |
5.E ~E — Channel settings · SendChanSettings service.c:1125
~E + device(byte) + deviceChan(byte) + C_PARAMS (io.h:60-73) + optional
8-byte CT serial. For the DA-07 build (USE_SCALE_OFFSET on,
NEED_C_PARAMS_DISPLAY_FIELD off) C_PARAMS is 27 bytes:
| Off | Size | Field |
|---|---|---|
| 0 | 1 | device (0xFF = unused) |
| 1 | 1 | active (b7 active, b6 disabled, b5 no-alarm, b4-0 calc type) |
| 2 | 16 | limits[4] = LO_ALR, LO_WRN, HI_WRN, HI_ALR (each float) |
| 18 | 4 | scale (float) |
| 22 | 4 | offset (float) |
| 26 | 1 | alarm (link to local alarm indicator) |
If the channel has a CT serial (gChans[c].serNo <= MAX_SERNO), 8 more bytes of |
||
serial are appended (service.c:1146-1150). |
5.W ~W — Universal-Modbus channel settings (07C only) · SendUModbusChanSettings service.c:1165
~W + device(byte) + chan(byte) + UMODBUS_PARAMS (packed, 17 bytes,
UniversalModbus.h:39-56). Only compiled for MODEL_DA07C. On plain DA-07/07B
this message never appears. Layout:
| Off | Size | Field | Meaning |
|---|---|---|---|
| 0 | 1 | OperMode |
how/when to read the measurement |
| 1 | 2 | DataRegNo |
Modbus reg # of first measurement register (LE) |
| 3 | 2 | TriggerRegNo |
reg # to test to trigger a read |
| 5 | 2 | TriggerMask |
AND-mask applied to the trigger register |
| 7 | 2 | StatusRegNo |
reg # holding status info |
| 9 | 7 | ErrorBitSet1..7 |
1 byte each — map status-register bits to CIMScan error bits |
| 16 | 1 | Options |
option flags |
The measurement decode type referenced by OperMode/Options uses these codes
(UniversalModbus.h:61-72): 0=UBYTE, 1=SBYTE, 2=UINT16, 3=INT16, 4=UINT32, 5=SINT32,
6=FLOAT32, 0xC=UINT32-reversed, 0xD=SINT32-reversed, 0xE=FLOAT32-reversed. Written
back via tool command ~D with setting 12 (sub-fields service.c:1667-1719).
5.F ~F — Device average values · SendDeviceAverages service.c:1093
~F + device(byte) + uTime(ulong, update time) + for each active device channel:
value(float, the averaged/scaled value sent to the server). Channel count =
GetChansByDeviceId(device).
5.G ~G — Device current (instantaneous) values · SendDeviceInputs service.c:1028
~G + device(byte) + iTime(ulong, input time) + for each active device channel:
status(byte) + input(float, raw/unscaled). Note: if svcDebugMode==1, the
per-channel status byte is replaced by the channel index (service.c:1070-1076)
— a debug aid the tool must account for when debug mode is on.
5.H ~H — Operational statistics (steady-state poll) · SendOperationalStatics service.c:1305
Sent once per second in SVC_POLL. Payload:
- 15 stat bytes =
svcStats[0..14]in the order ofservice.h:47-63(§7). recordCount(uint16, little-endian) = buffered NVM records (CountRecords()).Now()(ulong) = current time_t.MAX_DEVICES(16) nibbles =gDevice[i].statuslow nibble each (SendSvcNibble), device 0..15.- For each active alarm indicator group
i:i(byte) +local(nibble) +server(nibble) (service.c:1330-1338).
svcStats[0..7]are zeroed after each send (service.c:1320,1342); the higher-index stats are recomputed live each cycle (service.c:1309-1316).
5.K ~K — Diagnostic timing · SendDiagnostics service.c:1396
~K + podTime(uint16) + serTime(uint16) + svcTime(uint16) — per-subsystem
processing time since last query. Sent in response to tool command ~J.
5.M ~M — Alarm-indicator group settings · SendAlarmIndSettings service.c:1422
~M + index(byte) + active(byte) + addr[MAX_IND_ADDR] (8 bytes) — the device
addresses of the indicators in the group (A_PARAMS, io.h:313-317).
5.N ~N — Server alarm-update times · SendAlarmUpdateTimes service.c:1448
~N + for each indicator group n with a nonzero update time: n(byte) +
uTime(ulong). Sent when gAlarmUpdated is set.
5.P ~P — Channel name / CT serial number · SendChanName service.c:1205
~P + device(byte) + chan(byte) + 8-byte CT serial number. Only sent for
channels with a valid serNo.
5.R ~R — Display special message · SendDisplayMessage service.c:1520
~R + msgID(byte, bit-significant). SVC_MSG_REFRESH = 0x01 = "press the refresh
button" (service.h:105).
5.Y ~Y — Mirrored Modbus I/O frame · SendAcBufToSvcTool service.c:1000
~Y + direction ('1' = transmit to pods, '0' = received from pods) + the raw
Modbus frame bytes from acBuf as hex (acTail bytes). Emitted only while I/O
capture is enabled (§10).
5.J ~J — Modbus passthrough response (§9)
5.Z ~Z — ACK/NAK/IDLE (§4.1)
5.I ~I — Debug string — not compiled on DA-07 (#ifdef DEBUGGING, see §10).
5.x — Message types in the firmware header comment that are NOT emitted on DA-07
The service.c:1-305 header documents three additional firmware→tool messages that
have no emitter in the compiled tree (verified: no SendSvcChar('L'|'O'|'Q')
anywhere). A tool cross-referencing that header comment should treat them as dead:
~L(single-channel average) — superseded by theD_UPDATE_AVG → ~Fpath that re-sends a device's averages after a scale/offset edit.~O(RSSI values) — radio signal strength; relevant only to the wireless DA-33.~Q(rows to hide on the station tab) — no sender exists.
The complete set the DA-07 firmware actually emits is: A, B, C, D, E, F, G, H,
J, K, M, N, P, R, W, Y, Z (plus ~I only in a DEBUGGING build, and ~W only on
07C). That is the full §5 catalog.
6. Station-settings table (DA-07)
Index = position in the px[] table (service.c:437-453); sent in SVC_STATION,
written back via tool command ~B. For DA-07, DEBUGGING is off so index 29 is the
debug-level entry but carries no value bytes (see note). Editable flag and type are
from the label record (service.c:396-435); the value source/sink is the firmware
field shown.
| Idx | Label | Edit | Type | Bytes | Field (send service.c / recv service.c) |
|---|---|---|---|---|---|
| 1 | Station Name (16 chars) | B | 6 str | 16 | gStation.name (507 / 724) |
| 2 | Update Interval (sec) | B | 1 uint | 2 | gStation.updateInterval (512 / 738) |
| 3 | Reporting Interval (# updates) | B | 0 byte | 1 | gStation.reportInterval (516 / 753) |
| 4 | High 4 bytes of Serial Number | B | A | 4 | gStation.baseSN (520 / 768) |
| 5 | Comm-loss timeout (sec) | B | 1 uint | 2 | gStation.commsTimeout (524 / 783) |
| 6 | LAN MAC Address | C | 8 mac | 6 | gStation.xpMac (528 / —) |
| 7 | Local IP Address | B | 7 ip | 4 | gStation.xpLocalIp (532 / 787) |
| 8 | Local Port Number | B | 1 uint | 2 | gStation.xpLocalPort (536 / 792) |
| 9 | Subnet Mask Bits | B | 0 byte | 1 | gStation.xpSubnetBits (540 / 797) [host-bit count; bits→mask conversion buggy outside 1–8 — see BUG-ICD-13] |
| 10 | Gateway IP Address | B | 7 ip | 4 | gStation.xpGatewayIp (544 / 802) |
| 11 | Server's IP Address | B | 7 ip | 4 | gStation.xpRemoteIp (548 / 807) |
| 12 | Server's Port Number | B | 1 uint | 2 | gStation.xpRemotePort (552 / 812) |
| 13 | Model Number | C | 0 byte | 2 | gInfo.model (556 / —) |
| 14 | Firmware Version | C | 9 ver | 2 | gInfo.firmware (560 / —) |
| 15 | RS-485 Baud Rate | B | B baud | 2 | gStation.baudRate (564 / 817, re-inits USART0) |
| 16 | Poll Devices (0/1) | B | 0 byte | 1 | gStation.enablePolling (568 / 822) |
| 17 | Activation Energy (MKT) | B | 5 float | 4 | gStation.actEnergy (572 / 826) |
| 18 | Update Control (0=none 1=warn 2=alarm) | B | 0 byte | 1 | gStation.limitsUsage (576 / 831) |
| 19 | Pump Control Address | B | 0 byte | 1 | gStation.pumpCtrlAddr (580 / 843) |
| 20 | Flatline Detection (scans) | B | 0 byte | 1 | gStation.flatlineTOut (584 / 847) |
| 21 | Calibration Pressure (DP) | B | 5 float | 4 | gStation.calPres (588 / 851) |
| 22 | Barometric Pressure (DP & RH) | B | 5 float | 4 | gStation.baroPres (592 / 856) |
| 23 | Stacklight Style (0-4) | B | 0 byte | 1 | gStation.stacklights (596 / 861) [dead — see BUG-ICD-08] |
| 24 | Alarm Ind. Operating Mode (0-3) | B | 0 byte | 1 | gStation.alarmIndMode (600 / 865) |
| 25 | Beeper Operation (0-2) | B | 0 byte | 1 | gStation.beeperMode (604 / 869) |
| 26 | Buffer Operating Mode (0-3) | B | 0 byte | 1 | gStation.bufferMode (608 / 873) |
| 27 | NVRam Size (# Records) | C | 3 ulong | 4 | gMaxRecords (615 / —) |
| 28 | Modbus Timeout (ms) | B | 1 uint | 2 | gStation.modbusTimeOut (620 / 881, clamped 100–2000) |
Count: production DA-07 transmits exactly 28 station settings.
px[]has 28 entries — the index-29 DebugLevel record (p29) is#ifdef DEBUGGING, which is off in every shipped DA-07 build, soSVC_STATIONiterates 1..28 and never sends a 29th (service.c:424-453,1988). Indices 29-32 (Samples-per-Update, Reserved, Wireless Network, DebugLevel) exist only on DA-33/DA-09 or DEBUGGING builds. A tool counting station-setting frames should expect 28.
The label line number in the record (e.g.
016,021) is the legacy display row, not the index; the tool keys on index (position inpx[]).
7. The svcStats[15] block (in ~H)
Order is fixed by service.h:47-63; the tool must read them positionally:
| # | Symbol | Meaning |
|---|---|---|
| 0 | O_COUNT | outgoing Modbus message count |
| 1 | O_RETRIES | retries |
| 2 | O_VALUES | values sent |
| 3 | I_COUNT | incoming message count |
| 4 | I_CHECKSUM | messages with checksum errors |
| 5 | I_STRUCT | messages with structure errors |
| 6 | I_TRASH | discarded bytes |
| 7 | I_CHARS | incoming character count |
| 8 | P_ACTIVE | active pods (gInfo.devices) |
| 9 | P_ERROR | pods returning errors (gInfo.pdError) |
| 10 | P_COMM | pods in comm loss (gInfo.pdFail) |
| 11 | P_TRANS | transaction count |
| 12 | S_SENSOR | active channels (gInfo.sensors) |
| 13 | S_ERROR | channels in error |
| 14 | S_TIME | minutes since last message received |
Caution (BUG-ICD-05): the prose table in
service.c:72-87lists these in a different order (it swaps S_TIME/S_SENSOR and omits S_ERROR). Theservice.henum order above is authoritative — it is whatSendSvcHex(svcStats, MAX_SVC)actually transmits.
8. Enumerations
8.1 Device / pod types (DA-07 set, modbus.h:50-95)
The numeric type values the tool will see in ~D/~A device descriptors. (Values
forced to 255 in other models are normal here.)
| Val | Sym | Val | Sym | Val | Sym |
|---|---|---|---|---|---|
| 0 | xNONE | 16 | xP230 | 32 | xM6015 |
| 1 | xOLDCS05 | 17 | xP231 (particle ctr) | 33 | xMiraclean |
| 2 | xOLDCS10 | 18 | xPULSE (analog) | 34 | xKenomax3714 |
| 3 | xCS22 | 19 | xDOUT (digital out) | 35 | xKenomax3715 |
| 4 | xCS24 | 20 | xDIN (digital in) | 36 | xUniversalModbus |
| 5 | xCS41 | 21 | xM6003 (MetOne) | 37 | xM3413 |
| 6 | xPD15 | 22 | xCS05 | 38 | xM3415 |
| 7 | xPD15T | 23 | xPD90 | 39 | xM3445 |
| 8 | xPD16 | 24 | xCS10 | 40 | xM3423 |
| 9 | xPD17 | 25 | xDRAGER | 41 | xM3425 |
| 10 | xPD18 | 26 | xCI3100 (Climet) | 42 | xCS48 (Alphasense) |
| 11 | xPD19 | 27 | LightHouse | 43 | xGateway |
| 12 | xPD50 | 28 | sc200 | 44 | xCS31 (DQ node ctrl) |
| 13 | xPD51 | 29 | xPD51B | 45 | xRTA (RTA 460) |
| 14 | xPD52 | 30 | xM6005 | ||
| 15 | xPD68 (alarm) | 31 | xM6013 |
Which of these are selectable in the tool's device-type dropdown is reported at runtime by
GetMaxDevTypes()/SendDeviceInfo; do not hard-code the count. The per-type channel count and data type come fromSendDeviceInfo(modbus.c).
8.2 Channel status bits (~G status byte, io.h:140-156)
Low nibble = sensor error code (mutually-exclusive small values), high bits = alarm state:
| Bit/val | Sym | Meaning |
|---|---|---|
| 0x01 | C_ERR_UNDER | under-range |
| 0x02 | C_ERR_OVER | over-range |
| 0x03 | C_ERR_SENSOR | sensor error |
| 0x04 | C_ERR_EXCITE | RTD excitation / open T-C / flow |
| 0x08 | C_ERR_FLAT | flat-line |
| 0x10 | C_ERR_TRIM | offset/trim pending (CT sensors) |
| 0x20 | C_ERR_ACKED | alarm acknowledged at server |
| 0x40 | C_ERR_WARN | channel in warning |
| 0x80 | C_ERR_ALARM | channel in alarm |
(C_ERR_MASK=0x07 sensor bits; C_ERR_AMASK=0xC0 alarm bits.) |
8.3 Device status nibble (~H, io.h:246-255)
0x01 COM (no response), 0x02 FLOW, 0x03 ERR, 0x04 ACCURACY, 0x08 CAL-overdue,
0x40 WARN, 0x80 ALARM. (~H sends only the low nibble per device.)
8.4 Alarm-indicator state (~H local/server nibbles, io.h:307-311)
0x01 IND_OK, 0x02 IND_WARN, 0x04 IND_ALARM, 0x08 IND_ERROR. MAX_INDICATORS=16
groups, MAX_IND_ADDR=8 devices/group.
8.5 Station-config field encodings
stacklights0-4: 0=normal 4-level, 1=red, 2=red+grn, 3=red+yel+grn, 4=blu+red+yel+grn (config.h:141). Dead — not consumed by firmware (BUG-ICD-08).alarmIndMode0-3: none / local-only / server-else-local / both (config.h:155-159).beeperMode0-2: none / single chirp / continuous-until-cleared (config.h:143-147).bufferMode0-3: normal / 15-min+hourly / measurements-only / stats-only (config.h:149-153).limitsUsage0-2: none / warn / alarm (auto rate-change trigger).
9. Modbus passthrough (tool ↔ downstream pod)
The tool can inject a raw Modbus transaction onto the RS-485 bus and read the reply, without the firmware interpreting it.
Request — tool sends ~M (ProcIncomingMsg case 'M', service.c:2311-2317):
~ M aa ff rrrr nnnn CC CR
│ │ │ └ register/quantity count (uint16, MakeUInt — see note)
│ │ └────── starting register (uint16)
│ └───────── Modbus function code (byte)
└──────────── Modbus slave address (byte)
All fields ASCII-hex. The firmware loads testBuffer{addr,func,reg,cnt} and sets
testStatus = TEST_OUT (service.h:90-99). The Modbus engine then builds a proper
Modbus frame (address, function, register, count, CRC16) and transmits it on
RS-485 (ReceiveTestBuffer/SendModbusPoll, modbus.c).
Endianness (RESOLVED — big-endian):
reg/cntare parsed byMakeUInt(utility.c) and are sent big-endian ASCII hex ("0064"→0x0064). Confirmed against the VB6 builder:~M=M+Hex2(addr)+Hex2(func)+Hex4(register)+Hex4(value/count), all plain (non-reversed) hex (Test.frm:281-288). This is the one place service-frame hex is big-endian, unlike the little-endianSendSvcInt/SendSvcLongused for normal values — because the register goes straight onto the big-endian Modbus wire.
Downstream Modbus framing (what actually goes on RS-485):
addr · func · data · CRC16 (2 bytes, LSB first). Supported function codes
(modbus.h:7-15): 1 read-coil, 2 read-input, 3 read-reg, 4 read-AOUT, 5 write-coil,
6 write-reg, 15 write-mult-coils, 16 write-mult-regs, 24 read-FIFO.
Modbus CRC16: standard Modbus CRC-16 — polynomial 0xA001 (reflected 0x8005),
init 0xFFFF, refin/refout = true, no final XOR, appended little-endian
(low byte first). The firmware has two identical-result implementations: the bitwise
GetCRC (modbus.c:6003) actually computes the transmitted CRC, and a table-driven
GetCRC16 (crc.c:48-58) is used to check received frames. The transmit path writes
acBuf[6]=low, acBuf[7]=high (modbus.c:580-581); the read path confirms low-byte-
first (crc.c:149).
Response — firmware sends ~J (SendTestBuffer, service.c:1875-1903):
- On success:
~J+addr(byte) +func(byte) +byteCount(byte) +byteCountdata bytes. - On error/timeout (
testStatus == TEST_ERR):~J+00(single zero byte).
10. Debug mode & live I/O capture
10.1 I/O capture (Modbus mirroring) — available on DA-07
- The tool enables it with command
~Y1, disables with~Y0(setssvcSendIO,service.c:2382-2388). - While enabled, every Modbus frame the firmware sends to the pods is mirrored as
a
~Y1…frame (SendToDeviceEx→SendAcBufToSvcTool(XMT),usart.c:567-568), and every complete frame received from a pod triggers a~Y0…frame (usart.c:107-108,136-137, drained inmain.c:340-343). - In capture mode the firmware suspends the normal refresh state machine and only
watches for the tool's
~Zframes; if idle >25 s it auto-disables capture (service.c:2433-2467).
10.2 Debug mode (svcDebugMode) — available on DA-07
- Set with command
~Pnn(service.c:2350-2352). svcDebugMode == 1changes the~Gcurrent-values frame to send each channel's index in place of its status byte (service.c:1070-1076). Other values are reserved/no-op in the DA-07 build.
10.3 Debug strings (~I) — NOT available on DA-07
LoadDebugString/SendDebugString and the ~I message are wrapped in
#ifdef DEBUGGING (service.c:942-993, service.h:107-109). DEBUGGING is not
defined in any shipped DA-07 build (per DA-PQ-Xmega.cproj), so the firmware
never emits ~I, and station-setting index 29 carries no debug-level value. A tool
targeting production DA-07 units must not depend on ~I.
11. Tool → firmware command reference (summary)
All commands are ~<C><args>CC CR; firmware replies ~Z1 (ACK) on success or ~Z0
(NAK) on bad frame, except where a specific reply is noted. Source: ProcIncomingMsg
service.c:2253-2405.
| Cmd | Args | Action | Reply |
|---|---|---|---|
A |
— | Full refresh: svcState=SVC_START and start sequence |
begins ~A… stream |
B |
nn + value |
Update station setting nn (§6). Value DECIMAL, except name/baseSN(hex)/IP(dotted) — §3.1 |
~Z1 |
C |
dd nn + value |
Update device dd setting nn (1=type,2=addr,3=control,4=delay,5=serial[3]) service.c:1745. Value DECIMAL; serial[3] hex — §3.1 |
~Z1 |
D |
dd cc nn + value |
Update channel setting (1=active,2-5=limits,6=scale,7=offset,8=alarm,10=calc,11=name,12=UModbus[07C]) service.c:1547. Value DECIMAL float — §3.1 |
~Z1 |
E |
ii nn dd |
Update alarm-indicator ii (n=0 active flag, n≥1 addr[n-1]) service.c:1832 |
~Z1 |
F |
— | Request config update from server (QueueRequest(STATION_REQUEST_ID)) |
~Z1 |
G |
— | Clear outgoing buffer (ResetHistory()) |
~Z1 |
I |
— | Clear all channel disable flags | ~Z1 |
J |
— | Request diagnostics | ~K… (then no ~Z) |
K |
tttttttt |
Set RTC from time_t (MakeLong) |
~Z1 |
L |
— | Force server update of all sensors (SendAllSensorValues) |
~Z1 |
M |
aa ff rrrr nnnn |
Modbus passthrough (§9) | ~J… |
N |
— | Reset the controller (WaitForReset) |
— (resets; see BUG-ICD-01) |
O |
nn |
Set special-option byte (1=send err msg to server, 2=alarm-ack ind#1; 0x42=inert/harmful on DA-07, see §1) | ~Z1 |
P |
nn |
Set debug mode (svcDebugMode) |
~Z1 |
Q |
— | Erase all alarm-indicator settings | ~Z1 |
R |
n |
R0 = erase EEPROM + restore (svcRestore=TRUE); else reset |
~Z1/reset |
S |
nn |
Erase device nn |
~Z1 |
T |
nn |
Flag device nn for reset/reacquire (gResetID=nn+1) |
~Z1 |
X |
1 |
X1 = erase everything (all devices/settings) |
~Z1 |
Y |
n |
Enable(1)/disable(0) Modbus I/O capture (§10.1) |
— |
Z |
n |
Z1=ACK→send next; Z0=NAK→resend last |
— |
Several legacy/DA-33-only commands exist in the source but are inert on DA-07.
12. Worked byte-level examples
Spacing/comments added for clarity; the real frame has no spaces. Multi-byte values are little-endian hex (§3).
(a) Tool requests a full refresh.
Tool → ~A + checksum + CR.
Bytes: 7E 41 … checksum = (0x7E + 0x41) & 0xFF = 0xBF → "BF".
Frame on wire: 7E 41 42 46 0D = ~ABF\r.
(b) Firmware config frame (SVC_START). For a DA-07 with GetMaxDevTypes()=30:
~A 00 07 01 10 0A 1E 10 08 + CC + CR
= subtype 0, model 7, ver 1, MAX_DEVICES=0x10, MAX_DEVICE_CHANS=0x0A, types=0x1E,
indicators=0x10, addr/grp=0x08. Wire text: ~A000701100A1E1008 + 2-hex sum + \r.
(c) Operational-stats poll (~H). Layout: ~H + 15 stat bytes (30 hex) +
recordCount(uint16 LE, 4 hex) + Now()(ulong LE, 8 hex) + 16 device-status nibbles +
[per active indicator: idx byte + local nibble + server nibble] + CC + CR. To decode
Now(): take the 8 hex chars, reverse byte order, parse as time_t.
(d) Modbus passthrough read (~M/~J). Read 2 holding registers (func 3) at
register 0x0064 from slave 0x11:
Tool → ~M 11 03 0064 0002 + CC + CR (register/count big-endian — verify §9).
Firmware drives RS-485: 11 03 00 64 00 02 + CRC16(LE).
Firmware → ~J 11 03 04 <4 data bytes> + CC + CR (byteCount 0x04 = two
16-bit registers). On timeout: ~J00.
13. ICD-relevant bugs & quirks
This section is fully self-contained — every issue below carries its own description
and file:line evidence, and nothing here requires any other document. The bracketed
[ST-n] labels are optional cross-reference tags that match the same entries in the
firmware project's internal bug register; ignore them if you don't have it.
- BUG-ICD-00 [ST-13] (most important) Read/write encoding asymmetry: the
firmware sends values as little-endian hex but parses write-backs as decimal
ASCII (
atoi/atof), with per-field exceptions (names=ASCII, serials=hex, IPs=dotted-decimal,~Ealarm-indicator=hex both ways). See §3.1 — this is the #1 thing to get right in a rewrite. - BUG-ICD-01 [ST-1]
service.c:2319-2322:case 'N': WaitForReset();has no break/return and falls through intocase 'O'. IfWaitForReset()ever returns without resetting, the firmware then reinterprets the reset frame as a set-special- option command. Tools should not rely on~Nreturning an ACK. - BUG-ICD-02 [ST-8] Special option
0x42is inert/harmful on DA-07: sending~O42makesServiceTickearly-return (service.c:2430) and the service-port RX ISR tries to bridge to the uninitialized DNT radio UART (usart.c:258-263), so normal service comms freeze while nothing useful happens. A DA-07 tool must never send it (§1). - BUG-ICD-03 [ST-2, ST-3] service multi-byte values are little-endian
(
PrintHex), but the~Mregister/count and the~Kset-clock time are big-endian (and the clock is read back little-endian). Get endianness right per field (§3, §9). - BUG-ICD-04 [ST-5]
service.c:50-52 vs 386-387: contradictory data-type code tables (A/B/C). Use the lines 38-52 table. - BUG-ICD-05 [ST-6]
service.c:72-87 vs service.h:47-63:~Hstat order documented two ways. Theservice.henum order is what's actually sent. - BUG-ICD-06 [ST-3] (resolved)
service.c:2314-2315:~Mregister/count parsed byMakeUIntare big-endian (confirmed both from firmware and the VB6 builder), unlike the little-endian normal-value fields. - BUG-ICD-07 [ST-4] outbound checksum skips embedded zero bytes (
usart.c:1049) while inbound verify does not (service.c:1922); harmless for well-formed frames. - BUG-ICD-08 [ST-11]
gStation.stacklights(setting 23) is read/written by the tool but never consumed by the firmware — changing it has no effect. - BUG-ICD-09 [ST-7]
SendDeviceSettingsguardsif (device > MAX_DEVICES)(off-by-one; should be>=,service.c:1245). - BUG-ICD-10 [ST-9] the
~Idebug-string message is not compiled on DA-07 (#ifdef DEBUGGINGoff,service.c:942-993) — the firmware never emits it. Don't build a tool feature that waits for~I(§5.I, §10.3). - BUG-ICD-11 [ST-10] the
~Y(I/O-capture) and~Z(ack) command handlersreturnwithout sending an ACK (service.c:2382-2400); the steady refresh is ACK-driven (the tool advances it with~Z1), so not every command is ACKed (§4.1, §11). - BUG-ICD-12 [ST-12] tool→firmware string fields lose any literal
~(it is escaped to a space,usart.c:920-923) — e.g. a station name containing~is silently corrupted to a space (§2.2). - BUG-ICD-13 the subnet bits→mask conversion is buggy outside stored 1–8
(
netburner.c:464-481; found 2026-06-12 building the service tool's subnet editor).xpSubnetBitsis a HOST-bit count (trailing zeros of the mask — the legacy XPort encoding, "the number of zeros on the right side of the subnet mask",netburner.c:470-473). Two defects in the reconstruction: (1) a byte-swap precedence bug —netmaskIp & 0x0000FF00 << 8parses asnetmaskIp & (0x0000FF00 << 8)(netburner.c:475-476), so the two middle octets are never swapped during the endian reversal and stored 9–15 (/23…/17) reach the SBL2e Ethernet module with their middle octets exchanged (e.g. stored 9, meant as255.255.254.0, goes out as255.254.255.0— a non-contiguous, illegal mask); (2) a 16-bit shift overflow —(1 << bits)is a 16-bitinton the Xmega (netburner.c:474; the DA-12C path uses1UL), so for stored 16–255 the mask degenerates to0.0.0.0(strictly undefined behavior; collapse-to-0 is what the shipped avr-gcc/MCU does), killing the common /16 (stored 16) and /8 (stored 24). Only stored0(default →255.255.255.0,netburner.c:464-467, factory defaultconfig.c:135; unlike the DA-12C,255is NOT a sentinel) and1–8(masks /24…/31) are applied correctly. Likely never field-visible because the mask is ignored in DHCP mode. The Python tool therefore rejects subnet input outside 1–8 and flags read-back 9–255 (modules/da07/protocol/subnet.py, §6 row 9).
14. Appendix — VB6 / Python cross-check
Cross-checked against the legacy VB6 tool
(D:\Work\cimtechniques-service-suite\…\da07\legacy\) and the Python rewrite
(…\da07\). The DA-07 is the "eLink" in tool code.
14.1 Confirmed (firmware ↔ VB6 agree — high confidence)
- Framing:
~+ payload + 2-hex checksum + CR; no0xAA55, no length field, no byte-stuffing (bitstuff.basis only bit helpers). (Main.frm:3312-3329.) - Checksum: 8-bit additive sum of
~+payload, mod 256, 2 upper-hex; init 0. Identical to firmwareSendSvcCheck/SvcChecksumOK. - Serial: 9600,n,8,1, RTS asserted, NUL-discard, no flow control.
- Handshake: one-frame-per-ACK;
Z0=NAK→resend,Z1=ACK→next,Z2=idle; refresh always begins with outboundA; stream "ends" when~Hstats start (the tool infers end by a quiet timer — there is no end marker). - Encodings: all multi-byte values little-endian ASCII hex (
LeftEx); float = IEEE-754 single; TIME Unix-secs with a< 2009-01-01value shown as seconds-since-restart. Matches firmwarePrintHex+ theIsValidTimeepoch gate. ~M→~Jpassthrough: tool sends ASCIIM+addr+func+reg+count; the station builds the Modbus frame + CRC16 (the tool's own CRC16 inSupport.basis defined but never called).~J2-byte reply = error (00=CRC err,01=no response).- Modbus CRC16 params (poly 0xA001, init 0xFFFF, reflected) match
crc.c. - Station-setting typeCodes and the inbound message layouts (
A/B/C/D/E/F/G/H/ L/M/N/P/W/Y) match the firmware serializers documented in §5–§7.
14.2 Resolved open questions
~Mregister/count byte order = big-endian (§9). ✓~Kset-clock byte order: the tool sends the time big-endian (Hex8, no reversal) but reads times back little-endian. So the firmware'sMakeLongparse of~Kis big-endian, an asymmetry vs the little-endian~F/~G/~Htimes. The Python rewrite already flags this. (BUG ST-2.) ✓~Adevice-type descriptor layout filled in §5.A1. ✓~I/ DEBUGGING: the VB6 tool can display~Idebug records, but the DA-07 firmware never emits them (DEBUGGINGoff). Production DA-07 units simply never send~I. ✓
14.3 Tool-vs-firmware discrepancies (rewrite must heed)
- Outbound vs inbound letters reuse the same letter for different meanings. Track
direction. Outbound (tool→fw) catalog confirmed:
Arefresh,Bwrite-station- setting,Cwrite-device-field,Dwrite-channel-field,Ewrite-alarm-indicator,Frequest server config update (not a read),Gerase outgoing buffer (destructive),Iclear-disable/debug,Jdiagnostics (bare),Kset-clock,Lforce-update,Mpassthrough,Nreset,Ospecial-option,Pdebug-mode,Qclear-alarm-ind,R0/R1restore start/end,Sremove-device,Treset- device,X1erase-all,Y1/Y0I/O-capture,Zack. (Inbound meanings in §5.) - The Python rewrite currently mislabels
FandGas value-reads. They are NOT reads;Gerases the buffer. Fix in the rewrite (firmware-confirmed:case 'F'=QueueRequest(STATION_REQUEST_ID),case 'G'=ResetHistory(),service.c:2277-2291). - Commands the VB6 tool sends that the DA-07 firmware does NOT implement (would be
NAKed or behave differently — likely targeting other "eLink" products):
6(bulk-set device serial) — nocase '6'inProcIncomingMsg→ NAK.Jwith arguments (alarm-group membership,Multi.frm) — firmwarecase 'J'always just sends diagnostics, ignoring any args.M00/M01(switch eLink PodNet↔Modbus mode,Mode.frm) — firmwarecase 'M'treats these as a passthrough to address 0x00, not a mode switch.R1(the tool's "restore-stream end marker") — firmwarecase 'R'with a non-'0'arg callsWaitForReset(), i.e. the station resets. (Possibly the intended post-restore reboot, but the tool's intent label differs.)
~Echannel record trailing fields (RESOLVED — follow the firmware): the VB6 decoder reads…alarm, disp, name, but the DA-07 firmware emits nodispfield —C_PARAMSends atalarm(27 bytes total) becauseNEED_C_PARAMS_DISPLAY_FIELDis off (io.h:60-73), followed only by an optional 8-byte CT serial (service.c:1143-1150). The VB6'sdispread is therefore consuming a phantom byte (the first serial byte, or nothing). The rewrite should parse~Eper §5.E, not per the VB6.- eLink ASCII vs Modbus service mode: the tool declares
eLinkType(0=ASCII, 1=Modbus). Everything here is the ASCII path. No second (Modbus-framed) service protocol was found in the DA-07 firmware; treat ASCII as the only DA-07 service mode unless proven otherwise.