Final piece that lets the vendor extension framework introduced in
0e6f120 actually take effect on real frames.
Decoder (Gb32960MessageDecoder):
- Adds an overload decode(ByteBuffer, String platformAccount) alongside
the existing decode(ByteBuffer). Realtime/resend frames build a
Gb32960ParserContext(version, vin, platformAccount) and pass it to
bodyParser.parse(ctx, body) so the selector can pick a vendor profile.
- The legacy decode(ByteBuffer) delegates with a null account and is
preserved for tests and any caller that doesn't care about routing.
Channel handler (Gb32960ChannelHandler):
- Defines AttributeKey<String> PLATFORM_ACCOUNT_ATTR.
- On a successful 0x05 PLATFORM_LOGIN, stores the authenticated username
on ctx.channel().attr(PLATFORM_ACCOUNT_ATTR).
- On every channelRead0, reads the attribute (null for vehicle-direct
connections) and passes it to decoder.decode(buffer, account).
Autoconfig (Gb32960AutoConfiguration):
- Replaces the old single-registry wiring with three new beans:
* VendorExtensionCatalog with the built-in `guangdong-fc` entry
(the 6 GdFc* parsers from 957a86d)
* Gb32960ProfileRegistry, built from the union of all standard
InfoBlockParser beans plus, for each entry referenced in
lingniu.ingest.gb32960.vendor-extensions, a per-extension registry
that adds the catalog parsers on top of the defaults
* VendorExtensionSelector — RuleBasedVendorExtensionSelector when
vendor-extensions is non-empty, otherwise VendorExtensionSelector.NONE
- gb32960BodyParser now takes (Gb32960ProfileRegistry, VendorExtensionSelector).
Net effect: with the following yml fragment, lingniu account or any VIN
starting with LNVFC routes to the guangdong-fc profile and the previously
"unknown 0x30+" warns become real GdFc* InfoBlock entries:
lingniu.ingest.gb32960.vendor-extensions:
- name: guangdong-fc
match:
platform-accounts: [lingniu]
vin-prefixes: [LNVFC, LZG]
All existing 18 tests + 12 new tests (selector + vendor parsers) pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds 6 vendor parsers under codec/parser/vendor/guangdong/ implementing the
Guangdong "燃料电池汽车数据接入技术规范 v1.0" extension typeCodes that the
city's data ingestion platform sends inside V2016 frames:
0x30 GdFcStackBlockParser §7.2.3.4 表 13/14 fuel-cell stack
0x31 GdFcAuxiliaryBlockParser §7.2.3.5 表 15/16 auxiliary system
0x32 GdFcDcDcBlockParser §7.2.3.6 表 17 DC/DC
0x33 GdFcAirConditionerBlockParser §7.2.3.7 表 18 air conditioner
0x34 GdFcVehicleInfoBlockParser §7.2.3.8 表 19 vehicle info
0x80 GdFcDemoExtensionBlockParser §7.2.3.14 表 27 demo data extension
Each parser uses ProtocolVersion.V2016 + its respective typeCode and is
strictly Guangdong-spec layout, not the GB/T 32960.3-2025 fuel-cell stack
layout (the two share typeCode 0x30 but have different field orders and
extra cell-voltage statistics in the Guangdong variant).
Field model in InfoBlock.java gains:
- GdFcStack(stackCount, stacks: Stack(workState, waterTempC,
h2InletPressureKpa, airInletPressureKpa, airInletTempC, max/min/avg cell
voltage ids and values, cellCount, frameCellStart, frameCellCount,
frameCellVoltagesV))
- GdFcAuxiliary(subSystemCount, subsystems: Subsystem with air compressor,
hydrogen pump, water pump, PTC, low-voltage battery)
- GdFcDcDc, GdFcAirConditioner, GdFcVehicleInfo, GdFcDemoExtension
InfoBlockType enum gains the matching GD_FC_* constants.
Tests:
- GdFcParserTest covers the 4 fixed-length parsers (0x32/0x33/0x34/0x80)
with hand-crafted bytes asserting field-by-field decoded values.
- 0x30/0x31 are variable-length and will be exercised end-to-end via the
body parser integration test in a follow-up.
Spec doc: adds reference/广东燃料电池汽车示范应用城市群综合监管平台-...pdf
(v1.0.220822) used as the source of truth for these parsers.
These parsers exist as classes only; no Spring beans are wired yet.
Wiring + decoder context plumbing land in the next commit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a per-context routing layer for InfoBlock parsers, so different
peers can be routed to different parser sets (e.g. standard GB/T 32960
vs. a vendor extension that adds blocks in the 0x30~0x7F reserved range).
New types in protocol-gb32960:
- Gb32960ParserContext (record): version + vin + platformAccount tuple
- codec/profile/VendorExtensionCatalog: name -> list of vendor parsers
- codec/profile/Gb32960ProfileRegistry: name -> InfoBlockParserRegistry,
with a default registry built from the standard parsers and one extra
registry per enabled vendor extension (default + vendor parsers)
- codec/profile/VendorExtensionSelector: interface returning the matching
extension name (or null) for a given context
- codec/profile/RuleBasedVendorExtensionSelector: top-down first-match
scan over Gb32960Properties.vendorExtensions with a (account,vin)
cache keyed on case-insensitive normalized form
Gb32960Properties gains a `vendorExtensions` list with per-entry
`name` + `match` (platformAccounts / vins / vinPrefixes). All matching
fields are OR'd within an entry; entries are scanned top-down with
first-match-wins as documented.
Gb32960BodyParser keeps its legacy single-registry constructor for
back-compat (used by existing tests) and adds a new constructor that
takes the profile registry + selector. The parse loop now lives under
parse(Gb32960ParserContext, ByteBuffer); the legacy parse(version,body)
delegates to it with a minimal context (selector returns null -> default).
This commit only introduces the framework. Vendor parsers, autoconfig
wiring, and decoder/handler context plumbing land in follow-ups.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Captures the themed work that was rolled into the initial import commit
(login-ack time fix, idle handler, parser package split, removal of the
non-standard V2016 0x30 fuel cell stack registration, body-dump diagnostic
enhancement) so the history exists somewhere even though git can't carry
the per-theme commits.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>