@@ -173,6 +173,59 @@ public async Task Srdo_Length_Error_Sets_The_Communication_Bit_Until_The_Engine_
173173 await ExpectEmcyAsync ( wire , 3 , 0xFF00 , 0x00 ) ;
174174 }
175175
176+ // #434: the SRDO engine must not latch a disabled error before the node can report it.
177+ // RPDO is the existing contract; application EMCYs witness the exact wire sequence.
178+ [ Theory ]
179+ [ InlineData ( false ) ]
180+ [ InlineData ( true ) ]
181+ public async Task A_Length_Error_While_Emcy_Is_Disabled_Is_Reported_After_Enabling_For_Pdo_And_Srdo ( bool srdo )
182+ {
183+ var session = NewSession ( ) ;
184+ using var bus = Open ( session , 0 ) ;
185+ using var wire = new Wire ( session , 2 ) ;
186+ using var device = OpenClocked ( bus , Device , new ManualTimeSource ( ) ,
187+ new CanOpenNodeOptions { SrdoCount = 1 , WritableCommunicationParameters = true } ) ;
188+ await WaitForStateAsync ( device , NmtState . PreOperational ) ;
189+ var od = device . ObjectDictionary ;
190+ od . AddU16 ( 0x2000 , 0 , 0 ) ; od . AddU16 ( 0x2001 , 0 , 0 ) ;
191+ if ( srdo )
192+ {
193+ device . Safety ( ) . ConfigureSrdoConsumer ( 1 , new SrdoMapping ( ) . Add ( 0x2000 , 0 , 16 ) . Add ( 0x2001 , 0 , 16 ) ,
194+ TimeSpan . FromMilliseconds ( 100 ) , TimeSpan . FromMilliseconds ( 20 ) , 0x123 , 0x124 ) ;
195+ device . Safety ( ) . CommitSafetyConfiguration ( ) ;
196+ }
197+ else device . ConfigureRpdo ( 1 , new PdoMapping ( ) . Add ( 0x2000 , 0 , 16 ) . Add ( 0x2001 , 0 , 16 ) ) ;
198+ await StartAsync ( wire , device ) ;
199+ async Task Receive ( bool shortFrame )
200+ {
201+ byte [ ] normal = shortFrame ? new byte [ ] { 1 , 2 } : new byte [ ] { 1 , 2 , 3 , 4 } ;
202+ await Inject ( device , srdo ? 0x123 : Rpdo1 , normal ) ;
203+ if ( srdo ) await Inject ( device , 0x124 , SrdoFrames . Invert ( normal ) ) ;
204+ }
205+ od . WriteUnsigned ( 0x1014 , 0 , EmcyId | CanOpenCobId . InvalidBit ) ;
206+ await Receive ( shortFrame : true ) ;
207+ await Receive ( shortFrame : true ) ;
208+ od . WriteUnsigned ( 0x1014 , 0 , EmcyId ) ;
209+ await device . SendEmcyAsync ( 0xFF00 , 0 ) ;
210+ await ExpectEmcyAsync ( wire , 1 , 0xFF00 , 0 ) ;
211+ await Receive ( shortFrame : true ) ;
212+ await device . SendEmcyAsync ( 0xFF01 , 0 ) ;
213+ await ExpectEmcyAsync ( wire , 2 , 0x8210 , Communication ) ;
214+ await ExpectEmcyAsync ( wire , 3 , 0xFF01 , Communication ) ;
215+ od . ReadUnsigned ( 0x2000 , 0 ) . Should ( ) . Be ( 0 , "short frames remain unprocessed" ) ;
216+ od . WriteUnsigned ( 0x1014 , 0 , EmcyId | CanOpenCobId . InvalidBit ) ;
217+ await Receive ( shortFrame : true ) ;
218+ od . WriteUnsigned ( 0x1014 , 0 , EmcyId ) ;
219+ await Receive ( shortFrame : true ) ;
220+ await device . SendEmcyAsync ( 0xFF02 , 0 ) ;
221+ await ExpectEmcyAsync ( wire , 4 , 0xFF02 , Communication ) ;
222+ await Receive ( shortFrame : false ) ;
223+ await device . SendEmcyAsync ( 0xFF03 , 0 ) ;
224+ await ExpectEmcyAsync ( wire , 5 , ErrorReset , 0 ) ;
225+ await ExpectEmcyAsync ( wire , 6 , 0xFF03 , 0 ) ;
226+ wire . Count ( EmcyId ) . Should ( ) . Be ( 6 , "enabling an already reported run does not report it twice" ) ;
227+ }
228+
176229 // The rule across sources and the application: each new error is its own EMCY, the register
177230 // keeps bit 4 while any one is active, the application's bits are ORed in and never cleared by
178231 // the node, and the 0000h comes when the last communication error ends — with the
0 commit comments