Repository navigation
fix: correct PWM_OUT/ISELED/MDIO/ADC wire formats (#70, #71, #72, #77) - #91
Merged
Merged
Conversation
…, #77) Four endpoint-type conformance fixes, each verified against the OPEN Alliance TC18 Remote Control Protocol Specification's own figures/tables rather than against prior extraction summaries: - rcp/pwm.hpp (#70): PWM_OUT/PWM_IN's period/active_duration were uint32_t (8-byte payload); the spec's pwmo request format figure shows a fixed 4-byte payload (two big-endian 16-bit words, period then active — the existing field order was already correct). Narrowed both fields to uint16_t and added encode_pwm_payload/decode_pwm_payload for the exact 4-byte wire codec. Also removed PWM_OUT's reuse of GPIO's 8-way bitmask/saturating-write combinator: the spec's PWM_OUT request handling only ever applies a request's values directly, so handle_write now only honors WriteSemantics::Replace and rejects everything else as non-combinable. - rcp/iseled.hpp (#71): IseledResponse::address was a full uint16_t, ::data was an open vector<uint8_t>, and the header carried an invented 8-bit CRC (poly 0x07) its own comment admitted wasn't from the ISELED standard. The response format figure specifies a 12-bit address, a 12-bit data value, and an optional CRC whose real algorithm/width the ISELED standard (not TC18) defines. Repacked address/data to 12-bit range-validated fields and dropped the invented CRC outright rather than inventing a differently-sized one. - rcp/mdio.hpp (#72): replaced the invented IEEE 802.3 Clause 22/45 PHY-addressing model with the spec's actual mdio_mode/mdio_address/ mdio_payload model (Figure 42 + Table 57): a 2-bit mode selector, an opaque address field, and a payload that's 16 bits except for MMS multi-word access to MMS0/MMS1, which is 32 bits. - rcp/adc.hpp (#77): narrowed sample/result values from uint32_t to uint16_t throughout AdcEndpoint/compute_average, matching the spec's 16-bit ADC resolution ceiling and response-frame figures. Also removed the invented "ADC_NO_SIGNAL" identifier from the no-signal diagnostic message — Table 27 defines no ADC-specific no-signal error code (the only *_NO_SIGNAL entry, PWM_IN_NO_SIGNAL, belongs to PWM_IN). Updated .fusa-reqs.json requirement text for the affected REQ-PWM-002/003, REQ-ISELED-002/003, and REQ-MDIO-002/003/004 entries, and added a post-hoc fix-pass note to ROADMAP.md. All 53 ctest cases pass. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
…wing error
test_adc.cpp constructed std::optional<uint16_t> from bare int/unsigned
literals in several fixtures (added by the uint32_t->uint16_t ADC width
fix). GCC/Clang accept the implicit narrowing conversion silently, but
MSVC's warnings-as-errors build (C4244 -> C2220) fails on it. Wrap the
literals in explicit uint16_t{...} so the conversion is exact everywhere.
Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
This was referenced Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four endpoint-type wire-format/error-identity fixes, scoped strictly to
include/rcp/pwm.hpp,include/rcp/iseled.hpp,include/rcp/mdio.hpp,include/rcp/adc.hpp, their matching test files,.fusa-reqs.json, andROADMAP.md. No other file is touched. Each fix was verified directlyagainst the OPEN Alliance TC18 Remote Control Protocol Specification's own
figures/tables (via
pdftotext/pdftoppmon the local spec PDF), notagainst the prior extraction summaries the headers were originally written
from. No spec text is quoted anywhere below or in the code/comments —
behavior is described in my own words and cited by figure/table name only.
#70 —
include/rcp/pwm.hppPwmValue::period/active_durationwereuint32_t(an 8-byte payload).Verified against the "pwmo request format" figure (§13.7.5.3, Figure 26):
the payload is a fixed 4 bytes —
PWM_Period(big-endian 16 bit)followed by
PWM_active(big-endian 16 bit). The existing fieldorder (period, then active) was already correct — I re-checked this
against the figure myself before touching anything, per the task's
request to verify rather than trust the issue text, and confirmed no
swap was needed. Both fields are now
uint16_t, and I addedencode_pwm_payload/decode_pwm_payload(there was no wire codec atall before) implementing the exact 4-byte big-endian layout, reusing
rcp/avtp.hpp's existingput_u16/get_u16helpers the same wayrcp/gpio.hpp's payload codec already reusesput_u32/get_u32.combinator (
rcp::endpoint::apply_bitmask_write), which I was able todo cleanly rather than leaving it in place: the spec's PWM_OUT
request-handling section describes only direct application of a
request's period/active values (a request with
PWM_Periodof 0 stopsgeneration) — there's no described combination against the endpoint's
previous state the way GPIO's OR/AND/XOR/saturating-add/subtract make
sense for independent pin bits.
PwmOutEndpoint::handle_writenow onlyhonors
WriteSemantics::Replace; every other semantics is rejected viathe existing
EndpointErrc::non_combinable_write_semanticssentinel.#71 —
include/rcp/iseled.hppIseledResponse::addresswas a fulluint16_t,::datawas an openstd::vector<uint8_t>, and the header carried an invented 8-bit CRC(polynomial
0x07) its own prior comment admitted wasn't from theISELED standard. Verified against the "iseled request format" (Figure
40) and "iseled response format" (Figure 41) figures, §13.7.12.3: the
address field is 12 bits (in both request and response), and a
response's data is a single 12-bit value (
Data[11:0]), not an openbyte vector.
kIseledFieldMask/kIseledInstructionMaskplusvalidate_request/validate_responsenow enforce those widths.IseledRequest::datais left as a byte vector — Figure 40 shows therequest's data as variable-length (Data1/Data2/Data3/...+padding), so a
vector remains correct there; only the response's fixed-width fields
were wrong.
preference: I don't have a verified real ISELED-standard CRC-4
algorithm to source (the spec explicitly defers the ISELED CRC's actual
definition to the separate ISELED standard, which isn't in this repo),
so inventing a differently-sized polynomial would repeat the same
mistake.
IseledErrc::field_out_of_range(a direct, non-inventedconsequence of the width fix) takes the error category's place;
IseledSignal::NativeCrcErroris removed, leavingTransferCompleteasthe endpoint's only trigger signal.
#72 —
include/rcp/mdio.hppmodel (5-bit PHY address, Clause22/45 enum, device/register split) with
the spec's actual model, verified against the "mdio request format"
figure (Figure 42) and Table 57, §13.7.13.3: a 2-bit
mdio_modeselector, an
mdio_addressfield ("as per IEEE & OA SPI spec" — opaqueto this header, not decomposed further), and an
mdio_payloadwhosewidth depends on mode.
mdio_modevalues: I found that Table 57 itself prints01bforboth "MMD, single word access" and "MMD, multiple byte access" —
this can't be right for a 2-bit field where the other two rows (
10bMMS single,
11bMMS multi) are each printed once. Rather than guess, Iresolved it by elimination: since 3 of the 4 two-bit values are
unambiguous (
01bexplicit on the second MMD row,10b,11b), theremaining unclaimed value
00bmust be MMD single word access. Thisreasoning is documented at length in the header comment so it's
auditable, not asserted.
mdio_payloadwidth, per Table 57's own payload-width row: 16 bits forMMD (either sub-mode) and MMS single-word access; 32 bits for MMS
multi-(double-)word access specifically when the addressed MMS device is
MMS0 or MMS1 (16 bits for any other MMS device). Since which MMS
device number
mdio_addressencodes is itself deferred to the externalIEEE/OA SPI addressing scheme (not decoded by this header, consistent
with how
mdio_addressis treated as opaque throughout), I added anexplicit
MdioRequest::mms_is_0_or_1field the caller supplies, withpayload_width_bits(mode, mms_is_0_or_1)andvalidate_requestimplementing the exact rule.
#77 —
include/rcp/adc.hppuint32_ttouint16_tthroughoutAdcEndpoint/compute_average, matching the spec's 16-bit ADCresolution ceiling (§13.7.9.1) and "16 bit ADC value" response-frame
fields (Figure 32/34). Summation still widens internally to a 64-bit
accumulator to avoid overflow; only the stored/returned values are
16-bit. Added a boundary test averaging three
0xFFFFsamples toexercise this without truncation.
"ADC_NO_SIGNAL"string from the no-signaldiagnostic message. Checked Table 27 ("Error codes in responses",
§12.9.6) myself: there is no ADC-specific no-signal entry anywhere in
it — the only
*_NO_SIGNALcode in the whole table isPWM_IN_NO_SIGNAL(value 9), which belongs to PWM_IN, a differentendpoint type. Since nothing in Table 27 actually fits an ADC no-signal
condition,
AdcErrc::no_signal's message is now documented and renderedas a plain internal library condition with no claimed TC18 error-code
identity (kept conceptually — not nominally — parallel to PWM_IN's
no-signal handling).
Other changes
.fusa-reqs.json: updated the requirementtext/titleforREQ-PWM-002/003, REQ-ISELED-002/003, and REQ-MDIO-002/003/004 to match
the new behavior. Minimal diff (
git diff --statshows 14/14insertions/deletions) — only the affected entries changed.
ROADMAP.md: added a "Post-hoc wire-format fix pass (issues [cpp-RCP-01] PWM_OUT payload fields are 32-bit instead of the required 16-bit (field order is correct) #70, [cpp-RCP-02] ISELED response models wrong field widths and an invented CRC-8 instead of the native 4-bit CRC #71,[cpp-RCP-03] MDIO endpoint uses an invented Clause 22/45 PHY-address model instead of the spec's addressing scheme #72, [cpp-RCP-09] ADC endpoint uses an invented "ADC_NO_SIGNAL" error and 32-bit values instead of the spec's 16-bit width #77)" section after the existing "Post-hoc naming reconciliation"
section, following that section's own precedent for documenting a later
fix without rewriting the original milestone narrative.
What's not resolved / flagged explicitly
verified ISELED-standard CRC-4 algorithm becomes available, it should
replace the current "no CRC support" state rather than staying dropped
forever.
clang-tidycurrently reports "no checks enabled" on this repo (no.clang-tidyconfig file exists), and CI's ownclang-tidyjob runs itwrapped in
|| true, so it's a non-blocking no-op today regardless ofcode content — this predates this PR and I didn't add a
.clang-tidyconfig since that's out of scope for a wire-format fix. I additionally
ran
clang-tidylocally with an explicit--checkslist(
clang-analyzer-*,bugprone-*,performance-*) against both my changedheaders and an untouched sibling header (
rcp/gpio.hpp) for comparison— the same findings (enum base-type size suggestions, the standard
is_error_code_enum"std namespace modification" idiom everyrcp::*Errctype in this codebase already uses) appear identically inboth, confirming nothing new was introduced.
cpfusa(the FuSa CLI referenced byROADMAP.md/CI) isn't installed inthis environment, so I couldn't run
cpfusa check/lint/cyber/tracelocally; I updated
.fusa-reqs.jsonby hand for the requirements whosedescribed behavior changed.
Verification
cmake -B build -DCMAKE_BUILD_TYPE=Debug -G Ninja && cmake --build build --parallel— succeeds, no warnings from the touched files.ctest --test-dir build --output-on-failure— 53/53 tests pass,including all updated/new PWM, ISELED, MDIO, and ADC cases.
git status/git diff --stat— only the 10 in-scope files changed:.fusa-reqs.json,ROADMAP.md, the 4 headers, and their 4 test files.🤖 Generated with Claude Code