1use std::collections::{HashMap, HashSet};
65use std::net::Ipv4Addr;
66use std::time::{Duration, Instant};
67
68use crate::gvcp_server::FAKE_MAC;
69
70pub const VERSION: u64 = 0x0000;
72pub const DEVICE_MODE: u64 = 0x0004;
73pub const DEVICE_MAC_HIGH: u64 = 0x0008;
75pub const DEVICE_MAC_LOW: u64 = 0x000C;
77pub const SUPPORTED_IP_CONFIG: u64 = 0x0010;
78pub const CURRENT_IP_CONFIG: u64 = 0x0014;
79pub const CURRENT_IP_ADDRESS: u64 = 0x0024;
80pub const CURRENT_SUBNET_MASK: u64 = 0x0034;
81pub const CURRENT_DEFAULT_GATEWAY: u64 = 0x0044;
82pub const NUMBER_OF_MESSAGE_CHANNELS: u64 = 0x0900;
84pub const NUMBER_OF_STREAM_CHANNELS: u64 = 0x0904;
86
87pub const DEVICE_MODE_VALUE: u32 = 0x8000_0001;
90pub const PERSISTENT_IP_ADDRESS: u64 = 0x064C;
91pub const PERSISTENT_SUBNET_MASK: u64 = 0x065C;
92pub const PERSISTENT_DEFAULT_GATEWAY: u64 = 0x066C;
93pub const CCP: u64 = 0x0a00;
94pub const HEARTBEAT_TIMEOUT: u64 = 0x0938;
95pub const MESSAGE_CHANNEL_PORT: u64 = 0x0b00;
97pub const MESSAGE_CHANNEL_ADDRESS: u64 = 0x0b10;
99pub const STREAM_CHANNEL_BASE: u64 = 0x0d00;
100pub const STREAM_CHANNEL_STRIDE: u64 = 0x40;
102pub const SCP_HOST_PORT: u64 = 0x00;
103pub const SCP_PACKET_SIZE: u64 = 0x04;
104pub const SCP_PACKET_DELAY: u64 = 0x08;
105pub const SCP_DEST_ADDR: u64 = 0x18;
106
107pub const FIRST_URL_REG: u64 = 0x0200;
109pub const URL_REG_LEN: usize = 512;
110
111pub const XML_BLOB_BASE: u64 = 0x1_0000;
113
114pub const REG_WIDTH: u64 = 0x20000;
118pub const REG_HEIGHT: u64 = 0x20004;
119pub const REG_PIXEL_FORMAT: u64 = 0x20008;
120pub const REG_OFFSET_X: u64 = 0x2000c;
121pub const REG_OFFSET_Y: u64 = 0x20010;
122pub const REG_SENSOR_WIDTH: u64 = 0x20014;
123pub const REG_SENSOR_HEIGHT: u64 = 0x20018;
124
125pub const REG_ACQ_MODE: u64 = 0x20020;
127pub const REG_ACQ_START: u64 = 0x20024;
128pub const REG_ACQ_STOP: u64 = 0x20028;
129pub const REG_ACQ_FRAME_RATE: u64 = 0x2002c;
130
131pub const REG_EXPOSURE_TIME: u64 = 0x20030;
133pub const REG_EXPOSURE_AUTO: u64 = 0x20038;
134pub const REG_GAIN: u64 = 0x20040;
135pub const REG_GAIN_AUTO: u64 = 0x20048;
136pub const REG_BLACK_LEVEL: u64 = 0x20050;
137
138pub const REG_ACQ_FRAME_RATE_ENABLE: u64 = 0x20054;
147pub const REG_SENSOR_TYPE: u64 = 0x20058;
148
149pub const REG_DEVICE_CAPS: u64 = 0x2005c;
153
154pub const REG_STREAM_CHANNEL_SELECTOR: u64 = 0x20090;
159
160pub const REG_TIMESTAMP_FREQ: u64 = 0x20060;
162pub const REG_TIMESTAMP_VALUE: u64 = 0x20068;
163pub const REG_TIMESTAMP_LATCH: u64 = 0x20070;
164
165pub const REG_PTP_OFFSET_LATCHED: u64 = 0x20078;
172
173pub const REG_CHUNK_TIMESTAMP_LE: u64 = 0x20110;
178pub const REG_CHUNK_WIDTH_LE: u64 = 0x20118;
179
180pub const PTP_OFFSET_NS: i64 = -1_234_567;
185
186pub const CHUNK_TIMESTAMP_TICKS: u64 = 0x0000_0000_1234_5678;
191
192pub const CHUNK_WIDTH_PX: u32 = 1440;
195
196pub const REG_CHUNK_MODE_ACTIVE: u64 = 0x20080;
198pub const REG_CHUNK_SELECTOR: u64 = 0x20084;
199pub const REG_CHUNK_ENABLE: u64 = 0x20088;
200
201pub const REG_FEATURE_STATUS: u64 = 0x2009c;
213
214pub const REG_EVENT_SELECTOR: u64 = 0x200a0;
215pub const REG_EVENT_NOTIFICATION: u64 = 0x200a4;
222
223pub const DEFAULT_EXPOSURE_US: f64 = 5000.0;
233
234pub const REG_USER_SET_SELECTOR: u64 = 0x200a8;
235pub const REG_USER_SET_LOAD: u64 = 0x200ac;
237
238pub const REG_WIDTH_MIN: u64 = 0x20100;
240pub const REG_WIDTH_MAX: u64 = 0x20104;
241pub const REG_HEIGHT_MIN: u64 = 0x20108;
242pub const REG_HEIGHT_MAX: u64 = 0x2010c;
243
244pub const REG_DEVICE_MODEL_NAME: u64 = 0x20200;
246pub const REG_DEVICE_VENDOR_NAME: u64 = 0x20220;
247pub const REG_DEVICE_SERIAL_NUMBER: u64 = 0x20240;
248pub const REG_DEVICE_FIRMWARE_VERSION: u64 = 0x20260;
249pub const REG_DEVICE_ID: u64 = 0x20280;
250
251pub const FAKE_XML: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
270<RegisterDescription
271 ModelName="VivaCam Fake"
272 VendorName="vitavision.dev"
273 ToolTip="Simulated GigE Vision camera for testing"
274 StandardNameSpace="GEV"
275 SchemaMajorVersion="1"
276 SchemaMinorVersion="1"
277 SchemaSubMinorVersion="0"
278 MajorVersion="1"
279 MinorVersion="0"
280 SubMinorVersion="0"
281 ProductGuid="76697661-6361-6d00-0000-000000000000"
282 VersionGuid="76697661-6361-6d00-0000-000000000001">
283
284 <!-- ════════════════════════════════════════════════════════════════════
285 Category Hierarchy (SFNC Standard)
286 ════════════════════════════════════════════════════════════════════ -->
287
288 <Category Name="Root" NameSpace="Standard">
289 <pFeature>DeviceControl</pFeature>
290 <pFeature>ImageFormatControl</pFeature>
291 <pFeature>AcquisitionControl</pFeature>
292 <pFeature>AnalogControl</pFeature>
293 <pFeature>TransportLayerControl</pFeature>
294 <pFeature>ChunkDataControl</pFeature>
295 <pFeature>UserSetControl</pFeature>
296 </Category>
297
298 <Category Name="UserSetControl">
299 <DisplayName>User Set Control</DisplayName>
300 <pFeature>UserSetSelector</pFeature>
301 <pFeature>UserSetLoad</pFeature>
302 </Category>
303
304 <Category Name="DeviceControl">
305 <DisplayName>Device Control</DisplayName>
306 <pFeature>DeviceVendorName</pFeature>
307 <pFeature>DeviceModelName</pFeature>
308 <pFeature>DeviceSerialNumber</pFeature>
309 <pFeature>DeviceFirmwareVersion</pFeature>
310 <pFeature>DeviceID</pFeature>
311 </Category>
312
313 <Category Name="ImageFormatControl">
314 <DisplayName>Image Format Control</DisplayName>
315 <pFeature>SensorType</pFeature>
316 <pFeature>SensorWidth</pFeature>
317 <pFeature>SensorHeight</pFeature>
318 <pFeature>Width</pFeature>
319 <pFeature>Height</pFeature>
320 <pFeature>OffsetX</pFeature>
321 <pFeature>OffsetY</pFeature>
322 <pFeature>PixelFormat</pFeature>
323 </Category>
324
325 <Category Name="AcquisitionControl">
326 <DisplayName>Acquisition Control</DisplayName>
327 <pFeature>AcquisitionMode</pFeature>
328 <pFeature>AcquisitionStart</pFeature>
329 <pFeature>AcquisitionStop</pFeature>
330 <pFeature>AcquisitionFrameRateEnable</pFeature>
331 <pFeature>AcquisitionFrameRate</pFeature>
332 <pFeature>ExposureTime</pFeature>
333 <pFeature>ExposureAuto</pFeature>
334 </Category>
335
336 <Category Name="AnalogControl">
337 <DisplayName>Analog Control</DisplayName>
338 <pFeature>Gain</pFeature>
339 <pFeature>GainAuto</pFeature>
340 <pFeature>BlackLevel</pFeature>
341 </Category>
342
343 <Category Name="TransportLayerControl">
344 <DisplayName>Transport Layer Control</DisplayName>
345 <pFeature>GevTimestampTickFrequency</pFeature>
346 <pFeature>GevTimestampValue</pFeature>
347 <pFeature>TimestampLatch</pFeature>
348 <pFeature>GevIEEE1588OffsetFromMasterLatched_Val</pFeature>
349 <pFeature>ChunkTimestamp_Val</pFeature>
350 <pFeature>ChunkWidth_Val</pFeature>
351 </Category>
352
353 <Category Name="ChunkDataControl">
354 <DisplayName>Chunk Data Control</DisplayName>
355 <pFeature>ChunkModeActive</pFeature>
356 <pFeature>ChunkSelector</pFeature>
357 <pFeature>ChunkEnable</pFeature>
358 </Category>
359
360 <Category Name="EventControl">
361 <DisplayName>Event Control</DisplayName>
362 <pFeature>EventSelector</pFeature>
363 <pFeature>EventNotification</pFeature>
364 </Category>
365
366 <!-- ════════════════════════════════════════════════════════════════════
367 Device Control Features
368 ════════════════════════════════════════════════════════════════════ -->
369
370 <String Name="DeviceVendorName" NameSpace="Standard">
371 <ToolTip>Name of the device vendor</ToolTip>
372 <Address>0x20220</Address>
373 <Length>32</Length>
374 <AccessMode>RO</AccessMode>
375 </String>
376
377 <String Name="DeviceModelName" NameSpace="Standard">
378 <ToolTip>Name of the device model</ToolTip>
379 <Address>0x20200</Address>
380 <Length>32</Length>
381 <AccessMode>RO</AccessMode>
382 </String>
383
384 <String Name="DeviceSerialNumber" NameSpace="Standard">
385 <ToolTip>Serial number of the device</ToolTip>
386 <Address>0x20240</Address>
387 <Length>16</Length>
388 <AccessMode>RO</AccessMode>
389 </String>
390
391 <String Name="DeviceFirmwareVersion" NameSpace="Standard">
392 <ToolTip>Firmware version of the device</ToolTip>
393 <Address>0x20260</Address>
394 <Length>32</Length>
395 <AccessMode>RO</AccessMode>
396 </String>
397
398 <String Name="DeviceID" NameSpace="Standard">
399 <ToolTip>User-configurable device identifier</ToolTip>
400 <Address>0x20280</Address>
401 <Length>32</Length>
402 <AccessMode>RO</AccessMode>
403 </String>
404
405 <!-- ════════════════════════════════════════════════════════════════════
406 Image Format Control Features
407 ════════════════════════════════════════════════════════════════════ -->
408
409 <Integer Name="SensorWidth" NameSpace="Standard">
410 <ToolTip>Physical sensor width in pixels</ToolTip>
411 <Address>0x20014</Address>
412 <Length>4</Length>
413 <AccessMode>RO</AccessMode>
414 <Min>1</Min>
415 <Max>4096</Max>
416 <Sign>Unsigned</Sign>
417 <Endianess>BigEndian</Endianess>
418 </Integer>
419
420 <Integer Name="SensorHeight" NameSpace="Standard">
421 <ToolTip>Physical sensor height in pixels</ToolTip>
422 <Address>0x20018</Address>
423 <Length>4</Length>
424 <AccessMode>RO</AccessMode>
425 <Min>1</Min>
426 <Max>4096</Max>
427 <Sign>Unsigned</Sign>
428 <Endianess>BigEndian</Endianess>
429 </Integer>
430
431 <Integer Name="Width" NameSpace="Standard">
432 <ToolTip>Width of the image in pixels</ToolTip>
433 <Address>0x20000</Address>
434 <Length>4</Length>
435 <AccessMode>RW</AccessMode>
436 <pMin>WidthMin</pMin>
437 <pMax>WidthMax</pMax>
438 <Sign>Unsigned</Sign>
439 <Endianess>BigEndian</Endianess>
440 </Integer>
441 <IntReg Name="WidthMin"><Address>0x20100</Address><Length>4</Length><AccessMode>RO</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
442 <IntReg Name="WidthMax"><Address>0x20104</Address><Length>4</Length><AccessMode>RO</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
443
444 <Integer Name="Height" NameSpace="Standard">
445 <ToolTip>Height of the image in pixels</ToolTip>
446 <Address>0x20004</Address>
447 <Length>4</Length>
448 <AccessMode>RW</AccessMode>
449 <pMin>HeightMin</pMin>
450 <pMax>HeightMax</pMax>
451 <Sign>Unsigned</Sign>
452 <Endianess>BigEndian</Endianess>
453 </Integer>
454 <IntReg Name="HeightMin"><Address>0x20108</Address><Length>4</Length><AccessMode>RO</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
455 <IntReg Name="HeightMax"><Address>0x2010c</Address><Length>4</Length><AccessMode>RO</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
456
457 <Integer Name="OffsetX" NameSpace="Standard">
458 <ToolTip>Horizontal offset from the sensor origin</ToolTip>
459 <Address>0x2000c</Address>
460 <Length>4</Length>
461 <AccessMode>RW</AccessMode>
462 <Min>0</Min>
463 <Max>4096</Max>
464 <Sign>Unsigned</Sign>
465 <Endianess>BigEndian</Endianess>
466 </Integer>
467
468 <Integer Name="OffsetY" NameSpace="Standard">
469 <ToolTip>Vertical offset from the sensor origin</ToolTip>
470 <Address>0x20010</Address>
471 <Length>4</Length>
472 <AccessMode>RW</AccessMode>
473 <Min>0</Min>
474 <Max>4096</Max>
475 <Sign>Unsigned</Sign>
476 <Endianess>BigEndian</Endianess>
477 </Integer>
478
479 <Enumeration Name="SensorType" NameSpace="Standard">
480 <ToolTip>Sensor variant (Monochrome / BayerRG / Color). On real hardware this would be read-only sensor metadata; here it is RW so tests can reconfigure the simulator.</ToolTip>
481 <EnumEntry Name="Monochrome"><Value>0</Value></EnumEntry>
482 <EnumEntry Name="BayerRG"><Value>1</Value></EnumEntry>
483 <EnumEntry Name="Color"><Value>2</Value></EnumEntry>
484 <pValue>SensorTypeReg</pValue>
485 </Enumeration>
486 <IntReg Name="SensorTypeReg"><Address>0x20058</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
487
488 <Enumeration Name="PixelFormat" NameSpace="Standard">
489 <ToolTip>Format of the pixel data — entries are gated by SensorType</ToolTip>
490 <EnumEntry Name="Mono8" NameSpace="Standard">
491 <Value>0x01080001</Value>
492 <pIsImplemented>PfMono8Avail</pIsImplemented>
493 </EnumEntry>
494 <EnumEntry Name="Mono16" NameSpace="Standard">
495 <Value>0x01100007</Value>
496 <pIsImplemented>PfMono16Avail</pIsImplemented>
497 </EnumEntry>
498 <EnumEntry Name="RGB8" NameSpace="Standard">
499 <Value>0x02180014</Value>
500 <pIsImplemented>PfRGB8Avail</pIsImplemented>
501 </EnumEntry>
502 <EnumEntry Name="BayerRG8" NameSpace="Standard">
503 <Value>0x01080009</Value>
504 <pIsImplemented>PfBayerRG8Avail</pIsImplemented>
505 </EnumEntry>
506 <pValue>PixelFormatReg</pValue>
507 </Enumeration>
508 <IntReg Name="PixelFormatReg"><Address>0x20008</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
509
510 <!-- PixelFormat entry availability driven by SensorType:
511 Monochrome (0) → Mono8, Mono16
512 BayerRG (1) → BayerRG8
513 Color (2) → RGB8 -->
514 <IntSwissKnife Name="PfMono8Avail">
515 <Formula>ST = 0</Formula>
516 <pVariable Name="ST">SensorTypeReg</pVariable>
517 </IntSwissKnife>
518 <IntSwissKnife Name="PfMono16Avail">
519 <Formula>ST = 0</Formula>
520 <pVariable Name="ST">SensorTypeReg</pVariable>
521 </IntSwissKnife>
522 <IntSwissKnife Name="PfBayerRG8Avail">
523 <Formula>ST = 1</Formula>
524 <pVariable Name="ST">SensorTypeReg</pVariable>
525 </IntSwissKnife>
526 <IntSwissKnife Name="PfRGB8Avail">
527 <Formula>ST = 2</Formula>
528 <pVariable Name="ST">SensorTypeReg</pVariable>
529 </IntSwissKnife>
530
531 <!-- ════════════════════════════════════════════════════════════════════
532 Device capability inquiry bits
533
534 Real inquiry blocks are addressed as `<pAddress>` (the block base) plus
535 a fixed `<Address>` offset, and the individual bits come from a
536 <StructReg>. Both terms contribute: keeping only one reads the wrong
537 register, which is what made issue #35's camera misreport every
538 capability it had.
539 ════════════════════════════════════════════════════════════════════ -->
540 <IntSwissKnife Name="DeviceRegBaseAddress">
541 <Formula>0x20000</Formula>
542 </IntSwissKnife>
543 <StructReg Comment="Device capability inquiry">
544 <pAddress>DeviceRegBaseAddress</pAddress>
545 <Address>0x5C</Address>
546 <Length>4</Length>
547 <AccessMode>RO</AccessMode>
548 <Endianess>BigEndian</Endianess>
549 <StructEntry Name="FrameRateControlInq_Bit"><Bit>0</Bit></StructEntry>
550 <StructEntry Name="ChunkSupportInq_Bit"><Bit>1</Bit></StructEntry>
551 <StructEntry Name="SequencerInq_Bit"><Bit>2</Bit></StructEntry>
552 </StructReg>
553
554 <!-- ════════════════════════════════════════════════════════════════════
555 GigE Vision stream channel registers
556
557 Stream channel N lives at 0x0D00 + N * 0x40, so the packet size
558 register is a fixed <Address> plus a <pIndex> scaled by the channel
559 stride. Ignoring the <pIndex> term always addresses channel 0.
560 ════════════════════════════════════════════════════════════════════ -->
561 <Integer Name="GevStreamChannelSelector" NameSpace="Standard">
562 <ToolTip>Stream channel the GevSCP* features address</ToolTip>
563 <Address>0x20090</Address>
564 <Length>4</Length>
565 <AccessMode>RW</AccessMode>
566 <Sign>Unsigned</Sign>
567 <Min>0</Min>
568 <Max>1</Max>
569 <Endianess>BigEndian</Endianess>
570 </Integer>
571 <Integer Name="GevSCPSPacketSize" NameSpace="Standard">
572 <ToolTip>Stream channel packet size in bytes</ToolTip>
573 <Address>0x0D04</Address>
574 <pIndex Offset="0x40">GevStreamChannelSelector</pIndex>
575 <Length>4</Length>
576 <AccessMode>RW</AccessMode>
577 <Sign>Unsigned</Sign>
578 <Min>0</Min>
579 <Max>65535</Max>
580 <Endianess>BigEndian</Endianess>
581 </Integer>
582
583 <!-- ════════════════════════════════════════════════════════════════════
584 Acquisition Control Features
585 ════════════════════════════════════════════════════════════════════ -->
586
587 <Enumeration Name="AcquisitionMode" NameSpace="Standard">
588 <ToolTip>Camera acquisition mode</ToolTip>
589 <EnumEntry Name="Continuous"><Value>0</Value></EnumEntry>
590 <EnumEntry Name="SingleFrame"><Value>1</Value></EnumEntry>
591 <EnumEntry Name="MultiFrame"><Value>2</Value></EnumEntry>
592 <pValue>AcquisitionModeReg</pValue>
593 </Enumeration>
594 <IntReg Name="AcquisitionModeReg"><Address>0x20020</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
595
596 <Command Name="AcquisitionStart" NameSpace="Standard">
597 <ToolTip>Start image acquisition</ToolTip>
598 <Address>0x20024</Address>
599 <Length>4</Length>
600 <AccessMode>WO</AccessMode>
601 <CommandValue>1</CommandValue>
602 <Endianess>BigEndian</Endianess>
603 </Command>
604
605 <Enumeration Name="UserSetSelector" NameSpace="Standard">
606 <ToolTip>User set that UserSetLoad restores</ToolTip>
607 <EnumEntry Name="Default"><Value>0</Value></EnumEntry>
608 <EnumEntry Name="UserSet0"><Value>1</Value></EnumEntry>
609 <pValue>UserSetSelectorReg</pValue>
610 </Enumeration>
611 <IntReg Name="UserSetSelectorReg"><Address>0x200a8</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
612
613 <!-- Reaches its register through <pValue>, unlike the three commands above.
614 That is the shape every <Command> in the vendor corpus uses, and the one
615 our tests had no example of (backlog GA-10). -->
616 <Command Name="UserSetLoad" NameSpace="Standard">
617 <ToolTip>Restore the selected user set</ToolTip>
618 <pValue>UserSetLoadReg</pValue>
619 <CommandValue>1</CommandValue>
620 </Command>
621 <IntReg Name="UserSetLoadReg"><Address>0x200ac</Address><Length>4</Length><AccessMode>WO</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
622
623 <Command Name="AcquisitionStop" NameSpace="Standard">
624 <ToolTip>Stop image acquisition</ToolTip>
625 <Address>0x20028</Address>
626 <Length>4</Length>
627 <AccessMode>WO</AccessMode>
628 <CommandValue>1</CommandValue>
629 <Endianess>BigEndian</Endianess>
630 </Command>
631
632 <Boolean Name="AcquisitionFrameRateEnable" NameSpace="Standard">
633 <ToolTip>Enable manual control of AcquisitionFrameRate. When false, the frame rate is unavailable for read/write.</ToolTip>
634 <pValue>AcquisitionFrameRateEnableReg</pValue>
635 </Boolean>
636 <IntReg Name="AcquisitionFrameRateEnableReg"><Address>0x20054</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess>
637 <pIsImplemented>FrameRateControlInq_Bit</pIsImplemented>
638 </IntReg>
639
640 <Float Name="AcquisitionFrameRate" NameSpace="Standard">
641 <ToolTip>Target frame rate in Hz</ToolTip>
642 <Address>0x2002c</Address>
643 <Length>4</Length>
644 <AccessMode>RW</AccessMode>
645 <Min>1.0</Min>
646 <Max>120.0</Max>
647 <Endianess>BigEndian</Endianess>
648 <pIsAvailable>AcquisitionFrameRateEnable</pIsAvailable>
649 </Float>
650
651 <Float Name="ExposureTime" NameSpace="Standard">
652 <ToolTip>Exposure time in microseconds — locked to RO when ExposureAuto is not Off</ToolTip>
653 <Address>0x20030</Address>
654 <Length>8</Length>
655 <AccessMode>RW</AccessMode>
656 <Min>10.0</Min>
657 <Max>1000000.0</Max>
658 <Endianess>BigEndian</Endianess>
659 <pIsImplemented>ExposureTime_Imp</pIsImplemented>
660 <pIsAvailable>ExposureTime_Avl</pIsAvailable>
661 <pIsLocked>ExposureAutoActive</pIsLocked>
662 </Float>
663
664 <!-- Feature-status bits in one big-endian word, the shape FLIR ships and the
665 shape that exposed issue #120. GenICam counts <Bit> from the MSB here, so
666 bit 0 is 0x80000000 and bit 1 is 0x40000000; the fake boots this register
667 to 0xC0000000. Read from the wrong end these both come out zero and
668 ExposureTime becomes unavailable, which is what the reporter saw. -->
669 <MaskedIntReg Name="ExposureTime_Imp">
670 <Address>0x2009c</Address><Length>4</Length><AccessMode>RO</AccessMode>
671 <Bit>0</Bit><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess>
672 </MaskedIntReg>
673 <MaskedIntReg Name="ExposureTime_Avl">
674 <Address>0x2009c</Address><Length>4</Length><AccessMode>RO</AccessMode>
675 <Bit>1</Bit><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess>
676 </MaskedIntReg>
677
678 <Enumeration Name="ExposureAuto" NameSpace="Standard">
679 <ToolTip>Automatic exposure control</ToolTip>
680 <EnumEntry Name="Off"><Value>0</Value></EnumEntry>
681 <EnumEntry Name="Once"><Value>1</Value></EnumEntry>
682 <EnumEntry Name="Continuous"><Value>2</Value></EnumEntry>
683 <pValue>ExposureAutoReg</pValue>
684 </Enumeration>
685 <IntReg Name="ExposureAutoReg"><Address>0x20038</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
686
687 <IntSwissKnife Name="ExposureAutoActive">
688 <Formula>EA <> 0</Formula>
689 <pVariable Name="EA">ExposureAutoReg</pVariable>
690 </IntSwissKnife>
691
692 <!-- ════════════════════════════════════════════════════════════════════
693 Analog Control Features
694 ════════════════════════════════════════════════════════════════════ -->
695
696 <Float Name="Gain" NameSpace="Standard">
697 <!-- CDATA-wrapped tooltip, as shipped by several vendors: the literal `&`
698 and `<` inside are legal here and must survive parsing (issue #45). -->
699 <ToolTip><![CDATA[Gain applied to the image in dB — locked to RO when GainAuto is not Off (0 < gain & gain < 48)]]></ToolTip>
700 <Address>0x20040</Address>
701 <Length>8</Length>
702 <AccessMode>RW</AccessMode>
703 <Min>0.0</Min>
704 <Max>48.0</Max>
705 <Endianess>BigEndian</Endianess>
706 <pIsLocked>GainAutoActive</pIsLocked>
707 </Float>
708
709 <Enumeration Name="GainAuto" NameSpace="Standard">
710 <ToolTip>Automatic gain control</ToolTip>
711 <EnumEntry Name="Off"><Value>0</Value></EnumEntry>
712 <EnumEntry Name="Once"><Value>1</Value></EnumEntry>
713 <EnumEntry Name="Continuous"><Value>2</Value></EnumEntry>
714 <pValue>GainAutoReg</pValue>
715 </Enumeration>
716 <IntReg Name="GainAutoReg"><Address>0x20048</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
717
718 <IntSwissKnife Name="GainAutoActive">
719 <Formula>GA <> 0</Formula>
720 <pVariable Name="GA">GainAutoReg</pVariable>
721 </IntSwissKnife>
722
723 <Integer Name="BlackLevel" NameSpace="Standard">
724 <ToolTip>Analog black level offset</ToolTip>
725 <Address>0x20050</Address>
726 <Length>4</Length>
727 <AccessMode>RW</AccessMode>
728 <Min>0</Min>
729 <Max>255</Max>
730 <Sign>Unsigned</Sign>
731 <Endianess>BigEndian</Endianess>
732 </Integer>
733
734 <!-- ════════════════════════════════════════════════════════════════════
735 Transport Layer Control (Timestamp)
736 ════════════════════════════════════════════════════════════════════ -->
737
738 <Integer Name="GevTimestampTickFrequency" NameSpace="Standard">
739 <ToolTip>Device timestamp tick frequency in Hz (1 GHz)</ToolTip>
740 <Address>0x20060</Address>
741 <Length>4</Length>
742 <AccessMode>RO</AccessMode>
743 <Sign>Unsigned</Sign>
744 <Endianess>BigEndian</Endianess>
745 </Integer>
746
747 <Integer Name="GevTimestampValue" NameSpace="Standard">
748 <ToolTip>Current device timestamp in ticks (latched)</ToolTip>
749 <Address>0x20068</Address>
750 <Length>8</Length>
751 <AccessMode>RO</AccessMode>
752 <Sign>Unsigned</Sign>
753 <Endianess>BigEndian</Endianess>
754 </Integer>
755
756 <IntReg Name="GevIEEE1588OffsetFromMasterLatched_Val" NameSpace="Custom">
757 <ToolTip>PTP offset from master in ns, latched</ToolTip>
758 <Address>0x20078</Address>
759 <Length>8</Length>
760 <AccessMode>RO</AccessMode>
761 <Sign>Unsigned</Sign>
762 <Endianess>BigEndian</Endianess>
763 </IntReg>
764
765 <IntReg Name="ChunkTimestamp_Val" NameSpace="Custom">
766 <ToolTip>Chunk timestamp in ticks</ToolTip>
767 <Address>0x20110</Address>
768 <Length>8</Length>
769 <AccessMode>RO</AccessMode>
770 <Sign>Unsigned</Sign>
771 <Endianess>LittleEndian</Endianess>
772 </IntReg>
773
774 <IntReg Name="ChunkWidth_Val" NameSpace="Custom">
775 <ToolTip>Chunk image width in pixels</ToolTip>
776 <Address>0x20118</Address>
777 <Length>4</Length>
778 <AccessMode>RO</AccessMode>
779 <Sign>Unsigned</Sign>
780 <Endianess>LittleEndian</Endianess>
781 </IntReg>
782
783 <Command Name="TimestampLatch" NameSpace="Standard">
784 <ToolTip>Latch the current timestamp into GevTimestampValue</ToolTip>
785 <Address>0x20070</Address>
786 <Length>4</Length>
787 <AccessMode>WO</AccessMode>
788 <CommandValue>1</CommandValue>
789 <Endianess>BigEndian</Endianess>
790 </Command>
791
792 <!-- ════════════════════════════════════════════════════════════════════
793 Chunk Data Control
794 ════════════════════════════════════════════════════════════════════ -->
795
796 <!-- SFNC defines ChunkModeActive and ChunkEnable as IBoolean, and all 23
797 vendor-corpus documents that declare ChunkModeActive use <Boolean> over
798 a backing <IntReg> - none uses <Integer>. Declaring them as <Integer>
799 here made Camera::configure_chunks, which correctly calls set_bool,
800 fail against the only camera we can test with. -->
801 <Boolean Name="ChunkModeActive" NameSpace="Standard">
802 <ToolTip>Enable chunk data in image frames</ToolTip>
803 <pValue>ChunkModeActiveReg</pValue>
804 </Boolean>
805 <IntReg Name="ChunkModeActiveReg"><Address>0x20080</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
806
807 <Enumeration Name="ChunkSelector" NameSpace="Standard">
808 <ToolTip>Select which chunk feature to configure</ToolTip>
809 <EnumEntry Name="Timestamp"><Value>1</Value></EnumEntry>
810 <EnumEntry Name="ExposureTime"><Value>2</Value></EnumEntry>
811 <EnumEntry Name="Gain"><Value>3</Value></EnumEntry>
812 <pValue>ChunkSelectorReg</pValue>
813 </Enumeration>
814 <IntReg Name="ChunkSelectorReg"><Address>0x20084</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
815
816 <Boolean Name="ChunkEnable" NameSpace="Standard">
817 <ToolTip>Enable the selected chunk feature</ToolTip>
818 <pValue>ChunkEnableReg</pValue>
819 </Boolean>
820 <IntReg Name="ChunkEnableReg"><Address>0x20088</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
821
822 <!-- Event delivery is selected through GenApi, not through a bootstrap
823 register: EventSelector picks a GigE Vision event id and
824 EventNotification turns it on. The ids are the standard ones
825 (GEV_EVENT_START_OF_TRANSFER = 0x0005, END_OF_TRANSFER = 0x0006). -->
826 <Enumeration Name="EventSelector" NameSpace="Standard">
827 <ToolTip>Select which event to configure</ToolTip>
828 <EnumEntry Name="StartOfTransfer"><Value>5</Value></EnumEntry>
829 <EnumEntry Name="EndOfTransfer"><Value>6</Value></EnumEntry>
830 <pValue>EventSelectorReg</pValue>
831 </Enumeration>
832 <IntReg Name="EventSelectorReg"><Address>0x200A0</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
833
834 <Enumeration Name="EventNotification" NameSpace="Standard">
835 <ToolTip>Enable notification for the selected event</ToolTip>
836 <EnumEntry Name="Off"><Value>0</Value></EnumEntry>
837 <EnumEntry Name="On"><Value>1</Value></EnumEntry>
838 <pValue>EventNotificationReg</pValue>
839 </Enumeration>
840 <IntReg Name="EventNotificationReg"><Address>0x200A4</Address><Length>4</Length><AccessMode>RW</AccessMode><Sign>Unsigned</Sign><Endianess>BigEndian</Endianess></IntReg>
841
842</RegisterDescription>
843"#;
844
845pub struct RegisterMap {
852 regs: HashMap<u64, Vec<u8>>,
853 xml_blob: Vec<u8>,
854 clock_origin: Instant,
855 enabled_events: HashSet<u16>,
862 last_register_command: Instant,
864 enforce_heartbeat: bool,
866 max_packet_size: Option<u32>,
880 max_on_wire: Option<u32>,
888 pending_test_packet: Option<u32>,
891}
892
893pub const SCPS_FIRE_TEST_PACKET: u32 = 0x8000_0000;
895pub const SCPS_DO_NOT_FRAGMENT: u32 = 0x4000_0000;
897pub const STREAM_PACKET_SIZE_MASK: u32 = 0xFFFF;
899
900fn zip_xml_blob(xml: &[u8]) -> Vec<u8> {
902 use std::io::Write;
903 let mut writer = zip::ZipWriter::new(std::io::Cursor::new(Vec::new()));
904 let options = zip::write::SimpleFileOptions::default()
905 .compression_method(zip::CompressionMethod::Deflated);
906 writer
907 .start_file("fake.xml", options)
908 .expect("start XML zip entry");
909 writer.write_all(xml).expect("write XML zip entry");
910 writer.finish().expect("finish XML zip").into_inner()
911}
912
913impl RegisterMap {
914 pub fn new(width: u32, height: u32, pixel_format: u32, zip_xml: bool) -> Self {
921 let mut regs = HashMap::new();
922
923 regs.insert(VERSION, 0x0002_0000u32.to_be_bytes().to_vec()); regs.insert(DEVICE_MODE, DEVICE_MODE_VALUE.to_be_bytes().to_vec());
930 regs.insert(
931 DEVICE_MAC_HIGH,
932 u32::from(u16::from_be_bytes([FAKE_MAC[0], FAKE_MAC[1]]))
933 .to_be_bytes()
934 .to_vec(),
935 );
936 regs.insert(
937 DEVICE_MAC_LOW,
938 u32::from_be_bytes([FAKE_MAC[2], FAKE_MAC[3], FAKE_MAC[4], FAKE_MAC[5]])
939 .to_be_bytes()
940 .to_vec(),
941 );
942 regs.insert(SUPPORTED_IP_CONFIG, 0x8000_0007u32.to_be_bytes().to_vec());
944 regs.insert(CURRENT_IP_ADDRESS, Ipv4Addr::LOCALHOST.octets().to_vec());
945 regs.insert(CURRENT_SUBNET_MASK, [255, 0, 0, 0].to_vec());
946 regs.insert(CURRENT_DEFAULT_GATEWAY, vec![0, 0, 0, 0]);
947 regs.insert(NUMBER_OF_MESSAGE_CHANNELS, 1u32.to_be_bytes().to_vec());
948 regs.insert(NUMBER_OF_STREAM_CHANNELS, 1u32.to_be_bytes().to_vec());
949 regs.insert(CCP, vec![0, 0, 0, 0]);
950 regs.insert(HEARTBEAT_TIMEOUT, 3000u32.to_be_bytes().to_vec());
951 regs.insert(MESSAGE_CHANNEL_PORT, 0u32.to_be_bytes().to_vec());
952 regs.insert(MESSAGE_CHANNEL_ADDRESS, vec![0, 0, 0, 0]);
953
954 regs.insert(CURRENT_IP_CONFIG, 0x0000_0005u32.to_be_bytes().to_vec());
956 regs.insert(PERSISTENT_IP_ADDRESS, vec![0, 0, 0, 0]);
957 regs.insert(PERSISTENT_SUBNET_MASK, vec![0, 0, 0, 0]);
958 regs.insert(PERSISTENT_DEFAULT_GATEWAY, vec![0, 0, 0, 0]);
959
960 let base = STREAM_CHANNEL_BASE;
962 regs.insert(base + SCP_HOST_PORT, vec![0, 0, 0, 0]);
963 regs.insert(base + SCP_PACKET_SIZE, 1500u32.to_be_bytes().to_vec());
964 regs.insert(
967 base + STREAM_CHANNEL_STRIDE + SCP_PACKET_SIZE,
968 9000u32.to_be_bytes().to_vec(),
969 );
970 regs.insert(REG_STREAM_CHANNEL_SELECTOR, 0u32.to_be_bytes().to_vec());
971 regs.insert(base + SCP_PACKET_DELAY, vec![0, 0, 0, 0]);
972 regs.insert(base + SCP_DEST_ADDR, vec![0, 0, 0, 0]);
973
974 regs.insert(REG_DEVICE_MODEL_NAME, pad_string("VivaCam Fake", 32));
976 regs.insert(REG_DEVICE_VENDOR_NAME, pad_string("vitavision.dev", 32));
977 regs.insert(REG_DEVICE_SERIAL_NUMBER, pad_string("VIVA-FAKE-001", 16));
978 regs.insert(REG_DEVICE_FIRMWARE_VERSION, pad_string("1.0.0-fake", 32));
979 regs.insert(REG_DEVICE_ID, pad_string("VivaCam-0", 32));
980
981 regs.insert(REG_WIDTH, width.to_be_bytes().to_vec());
983 regs.insert(REG_HEIGHT, height.to_be_bytes().to_vec());
984 regs.insert(REG_PIXEL_FORMAT, pixel_format.to_be_bytes().to_vec());
985 regs.insert(REG_OFFSET_X, 0u32.to_be_bytes().to_vec());
986 regs.insert(REG_OFFSET_Y, 0u32.to_be_bytes().to_vec());
987 regs.insert(REG_SENSOR_WIDTH, 4096u32.to_be_bytes().to_vec());
988 regs.insert(REG_SENSOR_HEIGHT, 4096u32.to_be_bytes().to_vec());
989
990 regs.insert(REG_WIDTH_MIN, 16u32.to_be_bytes().to_vec());
992 regs.insert(REG_WIDTH_MAX, 4096u32.to_be_bytes().to_vec());
993 regs.insert(REG_HEIGHT_MIN, 16u32.to_be_bytes().to_vec());
994 regs.insert(REG_HEIGHT_MAX, 4096u32.to_be_bytes().to_vec());
995
996 regs.insert(REG_ACQ_MODE, 0u32.to_be_bytes().to_vec()); regs.insert(REG_ACQ_START, vec![0, 0, 0, 0]);
999 regs.insert(REG_USER_SET_SELECTOR, 0u32.to_be_bytes().to_vec()); regs.insert(REG_USER_SET_LOAD, 0u32.to_be_bytes().to_vec());
1001 regs.insert(REG_ACQ_STOP, vec![0, 0, 0, 0]);
1002 regs.insert(REG_ACQ_FRAME_RATE, 30.0f32.to_be_bytes().to_vec());
1003 regs.insert(
1004 REG_EXPOSURE_TIME,
1005 DEFAULT_EXPOSURE_US.to_be_bytes().to_vec(),
1006 );
1007 regs.insert(REG_EXPOSURE_AUTO, 0u32.to_be_bytes().to_vec()); regs.insert(REG_GAIN, 0.0f64.to_be_bytes().to_vec());
1011 regs.insert(REG_GAIN_AUTO, 0u32.to_be_bytes().to_vec()); regs.insert(REG_BLACK_LEVEL, 0u32.to_be_bytes().to_vec());
1013
1014 regs.insert(REG_ACQ_FRAME_RATE_ENABLE, 1u32.to_be_bytes().to_vec());
1019 regs.insert(REG_SENSOR_TYPE, 0u32.to_be_bytes().to_vec());
1020 regs.insert(REG_DEVICE_CAPS, 0xC000_0000u32.to_be_bytes().to_vec());
1023 regs.insert(REG_FEATURE_STATUS, 0xC000_0000u32.to_be_bytes().to_vec());
1026
1027 regs.insert(REG_TIMESTAMP_FREQ, 1_000_000_000u32.to_be_bytes().to_vec());
1029 regs.insert(REG_TIMESTAMP_VALUE, vec![0u8; 8]);
1030 regs.insert(REG_TIMESTAMP_LATCH, vec![0, 0, 0, 0]);
1031 regs.insert(
1032 REG_PTP_OFFSET_LATCHED,
1033 (PTP_OFFSET_NS as u64).to_be_bytes().to_vec(),
1034 );
1035 regs.insert(
1036 REG_CHUNK_TIMESTAMP_LE,
1037 CHUNK_TIMESTAMP_TICKS.to_le_bytes().to_vec(),
1038 );
1039 regs.insert(REG_CHUNK_WIDTH_LE, CHUNK_WIDTH_PX.to_le_bytes().to_vec());
1040
1041 regs.insert(REG_CHUNK_MODE_ACTIVE, 0u32.to_be_bytes().to_vec());
1043 regs.insert(REG_CHUNK_SELECTOR, 1u32.to_be_bytes().to_vec()); regs.insert(REG_CHUNK_ENABLE, 0u32.to_be_bytes().to_vec());
1045 regs.insert(REG_EVENT_SELECTOR, 5u32.to_be_bytes().to_vec());
1046
1047 let (xml_blob, xml_name) = if zip_xml {
1049 (zip_xml_blob(FAKE_XML.as_bytes()), "fake.zip")
1050 } else {
1051 (FAKE_XML.as_bytes().to_vec(), "fake.xml")
1052 };
1053 let url = format!(
1054 "Local:{xml_name};{:x};{:x}\0",
1055 XML_BLOB_BASE,
1056 xml_blob.len()
1057 );
1058 let mut url_bytes = vec![0u8; URL_REG_LEN];
1059 let src = url.as_bytes();
1060 url_bytes[..src.len()].copy_from_slice(src);
1061 regs.insert(FIRST_URL_REG, url_bytes);
1062
1063 Self {
1064 regs,
1065 xml_blob,
1066 clock_origin: Instant::now(),
1067 enabled_events: HashSet::new(),
1068 last_register_command: Instant::now(),
1069 enforce_heartbeat: false,
1070 max_packet_size: None,
1071 max_on_wire: None,
1072 pending_test_packet: None,
1073 }
1074 }
1075
1076 pub fn enforce_heartbeat(&mut self, enable: bool) {
1090 self.enforce_heartbeat = enable;
1091 self.last_register_command = Instant::now();
1092 }
1093
1094 pub fn set_max_on_wire(&mut self, max: u32) {
1105 self.max_on_wire = Some(max);
1106 }
1107
1108 pub fn max_on_wire(&self) -> Option<u32> {
1110 self.max_on_wire
1111 }
1112
1113 pub fn take_pending_test_packet(&mut self) -> Option<u32> {
1115 self.pending_test_packet.take()
1116 }
1117
1118 pub fn set_max_packet_size(&mut self, max: u32) {
1119 self.max_packet_size = Some(max);
1120 let current = self.stream_packet_size();
1123 if current > max {
1124 self.write(STREAM_CHANNEL_BASE + SCP_PACKET_SIZE, &max.to_be_bytes());
1125 }
1126 }
1127
1128 pub fn note_register_command(&mut self) -> bool {
1136 let elapsed = self.last_register_command.elapsed();
1137 self.last_register_command = Instant::now();
1138 if !self.enforce_heartbeat {
1139 return false;
1140 }
1141 let timeout = Duration::from_millis(u64::from(self.heartbeat_timeout_ms()));
1142 if timeout.is_zero() || elapsed <= timeout {
1143 return false;
1144 }
1145 let ccp = self.read(CCP, 4);
1146 if u32::from_be_bytes([ccp[0], ccp[1], ccp[2], ccp[3]]) == 0 {
1147 return false;
1148 }
1149 self.write(CCP, &0u32.to_be_bytes());
1150 true
1151 }
1152
1153 pub fn set_heartbeat_timeout_ms(&mut self, timeout_ms: u32) {
1159 self.write(HEARTBEAT_TIMEOUT, &timeout_ms.to_be_bytes());
1160 }
1161
1162 pub fn heartbeat_timeout_ms(&self) -> u32 {
1164 let data = self.read(HEARTBEAT_TIMEOUT, 4);
1165 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1166 }
1167
1168 pub fn read(&self, addr: u64, len: usize) -> Vec<u8> {
1170 if addr >= XML_BLOB_BASE {
1172 let offset = (addr - XML_BLOB_BASE) as usize;
1173 if offset < self.xml_blob.len() {
1174 let end = (offset + len).min(self.xml_blob.len());
1175 let mut result = self.xml_blob[offset..end].to_vec();
1176 result.resize(len, 0);
1177 return result;
1178 }
1179 }
1180
1181 if addr == REG_EVENT_NOTIFICATION {
1183 let on = u32::from(self.event_notification_on(self.event_selector()));
1184 let mut result = on.to_be_bytes().to_vec();
1185 result.resize(len, 0);
1186 result.truncate(len);
1187 return result;
1188 }
1189
1190 if let Some(data) = self.regs.get(&addr) {
1192 let mut result = data.clone();
1193 result.resize(len, 0);
1194 result.truncate(len);
1195 return result;
1196 }
1197
1198 for (®_addr, data) in &self.regs {
1200 if addr >= reg_addr && (addr - reg_addr) < data.len() as u64 {
1201 let offset = (addr - reg_addr) as usize;
1202 let end = (offset + len).min(data.len());
1203 let mut result = data[offset..end].to_vec();
1204 result.resize(len, 0);
1205 return result;
1206 }
1207 }
1208
1209 vec![0u8; len]
1210 }
1211
1212 pub fn write(&mut self, addr: u64, data: &[u8]) {
1214 let stripped;
1220 let mut data = data;
1221 if addr == STREAM_CHANNEL_BASE + SCP_PACKET_SIZE && data.len() >= 4 {
1222 let raw = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
1223 self.pending_test_packet =
1224 (raw & SCPS_FIRE_TEST_PACKET != 0).then_some(raw & STREAM_PACKET_SIZE_MASK);
1225 if raw & (SCPS_FIRE_TEST_PACKET | SCPS_DO_NOT_FRAGMENT) != 0 {
1226 stripped = (raw & STREAM_PACKET_SIZE_MASK).to_be_bytes();
1227 data = &stripped[..];
1228 }
1229 }
1230
1231 let clamped;
1234 let data = match self.max_packet_size {
1235 Some(max)
1236 if addr == STREAM_CHANNEL_BASE + SCP_PACKET_SIZE
1237 && data.len() >= 4
1238 && u32::from_be_bytes([data[0], data[1], data[2], data[3]]) > max =>
1239 {
1240 clamped = max.to_be_bytes();
1241 &clamped[..]
1242 }
1243 _ => data,
1244 };
1245
1246 if addr == REG_EVENT_NOTIFICATION && data.len() >= 4 {
1250 let event = self.event_selector();
1251 if u32::from_be_bytes([data[0], data[1], data[2], data[3]]) != 0 {
1252 self.enabled_events.insert(event);
1253 } else {
1254 self.enabled_events.remove(&event);
1255 }
1256 return;
1257 }
1258
1259 if let Some(existing) = self.regs.get_mut(&addr) {
1260 let len = existing.len().min(data.len());
1261 existing[..len].copy_from_slice(&data[..len]);
1262 return;
1263 }
1264
1265 let addrs: Vec<u64> = self.regs.keys().copied().collect();
1267 for reg_addr in addrs {
1268 let reg_data = self.regs.get(®_addr).unwrap();
1269 if addr >= reg_addr && (addr - reg_addr) < reg_data.len() as u64 {
1270 let offset = (addr - reg_addr) as usize;
1271 let end = (offset + data.len()).min(reg_data.len());
1272 let reg_data = self.regs.get_mut(®_addr).unwrap();
1273 reg_data[offset..end].copy_from_slice(&data[..end - offset]);
1274 return;
1275 }
1276 }
1277
1278 self.regs.insert(addr, data.to_vec());
1279 }
1280
1281 pub fn handle_special_write(&mut self, addr: u64) {
1283 if addr == REG_TIMESTAMP_LATCH {
1284 let ts = self.device_timestamp();
1285 self.regs
1286 .insert(REG_TIMESTAMP_VALUE, ts.to_be_bytes().to_vec());
1287 }
1288 if addr == REG_USER_SET_LOAD {
1289 self.load_user_set();
1290 }
1291 }
1292
1293 fn load_user_set(&mut self) {
1302 self.regs.insert(
1303 REG_EXPOSURE_TIME,
1304 DEFAULT_EXPOSURE_US.to_be_bytes().to_vec(),
1305 );
1306 self.regs.insert(REG_GAIN, 0.0f64.to_be_bytes().to_vec());
1307 self.regs
1308 .insert(REG_EXPOSURE_AUTO, 0u32.to_be_bytes().to_vec());
1309 self.regs.insert(REG_GAIN_AUTO, 0u32.to_be_bytes().to_vec());
1310 }
1311
1312 pub fn device_timestamp(&self) -> u64 {
1316 self.clock_origin.elapsed().as_nanos() as u64
1317 }
1318
1319 pub fn stream_dest_ip(&self) -> Ipv4Addr {
1321 let data = self.read(STREAM_CHANNEL_BASE + SCP_DEST_ADDR, 4);
1322 Ipv4Addr::new(data[0], data[1], data[2], data[3])
1323 }
1324
1325 pub fn stream_dest_port(&self) -> u16 {
1327 let data = self.read(STREAM_CHANNEL_BASE + SCP_HOST_PORT, 4);
1328 u16::from_be_bytes([data[2], data[3]])
1329 }
1330
1331 pub fn message_dest_ip(&self) -> Ipv4Addr {
1333 let data = self.read(MESSAGE_CHANNEL_ADDRESS, 4);
1334 Ipv4Addr::new(data[0], data[1], data[2], data[3])
1335 }
1336
1337 pub fn message_dest_port(&self) -> u16 {
1339 let data = self.read(MESSAGE_CHANNEL_PORT, 4);
1340 u16::from_be_bytes([data[2], data[3]])
1341 }
1342
1343 pub fn event_selector(&self) -> u16 {
1345 let data = self.read(REG_EVENT_SELECTOR, 4);
1346 u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as u16
1347 }
1348
1349 pub fn event_notification_on(&self, event_id: u16) -> bool {
1355 self.enabled_events.contains(&event_id)
1356 }
1357
1358 pub fn stream_packet_size(&self) -> u32 {
1360 let data = self.read(STREAM_CHANNEL_BASE + SCP_PACKET_SIZE, 4);
1361 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1362 }
1363
1364 pub fn width(&self) -> u32 {
1366 let data = self.read(REG_WIDTH, 4);
1367 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1368 }
1369
1370 pub fn height(&self) -> u32 {
1372 let data = self.read(REG_HEIGHT, 4);
1373 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1374 }
1375
1376 pub fn pixel_format_code(&self) -> u32 {
1378 let data = self.read(REG_PIXEL_FORMAT, 4);
1379 u32::from_be_bytes([data[0], data[1], data[2], data[3]])
1380 }
1381
1382 pub fn chunk_mode_active(&self) -> bool {
1384 let data = self.read(REG_CHUNK_MODE_ACTIVE, 4);
1385 u32::from_be_bytes([data[0], data[1], data[2], data[3]]) != 0
1386 }
1387
1388 pub fn exposure_time(&self) -> f64 {
1390 let data = self.read(REG_EXPOSURE_TIME, 8);
1391 f64::from_be_bytes([
1392 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1393 ])
1394 }
1395}
1396
1397fn pad_string(s: &str, len: usize) -> Vec<u8> {
1399 let mut buf = vec![0u8; len];
1400 let src = s.as_bytes();
1401 let copy_len = src.len().min(len);
1402 buf[..copy_len].copy_from_slice(&src[..copy_len]);
1403 buf
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408 use super::*;
1409
1410 fn map() -> RegisterMap {
1411 RegisterMap::new(64, 48, 0x0108_0001, false)
1412 }
1413
1414 fn select_and_enable(regs: &mut RegisterMap, event: u32, on: bool) {
1415 regs.write(REG_EVENT_SELECTOR, &event.to_be_bytes());
1416 regs.write(REG_EVENT_NOTIFICATION, &u32::from(on).to_be_bytes());
1417 }
1418
1419 #[test]
1424 fn enabling_a_second_event_leaves_the_first_enabled() {
1425 let mut regs = map();
1426 select_and_enable(&mut regs, 5, true);
1427 select_and_enable(&mut regs, 6, true);
1428 assert!(regs.event_notification_on(5));
1429 assert!(regs.event_notification_on(6));
1430 }
1431
1432 #[test]
1433 fn disabling_one_event_leaves_the_others_alone() {
1434 let mut regs = map();
1435 select_and_enable(&mut regs, 5, true);
1436 select_and_enable(&mut regs, 6, true);
1437 select_and_enable(&mut regs, 5, false);
1438 assert!(!regs.event_notification_on(5));
1439 assert!(regs.event_notification_on(6));
1440 }
1441
1442 #[test]
1446 fn the_bootstrap_mac_matches_the_discovery_mac() {
1447 let regs = map();
1448 let high = regs.read(DEVICE_MAC_HIGH, 4);
1449 let low = regs.read(DEVICE_MAC_LOW, 4);
1450 assert_eq!(&high[..2], &[0, 0], "top two bytes are reserved");
1451 let mac = [high[2], high[3], low[0], low[1], low[2], low[3]];
1452 assert_eq!(mac, FAKE_MAC);
1453 }
1454
1455 #[test]
1456 fn the_device_reports_one_channel_of_each_kind() {
1457 let regs = map();
1458 assert_eq!(regs.read(NUMBER_OF_MESSAGE_CHANNELS, 4), vec![0, 0, 0, 1]);
1459 assert_eq!(regs.read(NUMBER_OF_STREAM_CHANNELS, 4), vec![0, 0, 0, 1]);
1460 }
1461
1462 #[test]
1463 fn no_event_is_enabled_by_default() {
1464 let regs = map();
1465 assert!(!regs.event_notification_on(5));
1466 assert!(!regs.event_notification_on(6));
1467 }
1468
1469 #[test]
1472 fn reading_notification_follows_the_selector() {
1473 let mut regs = map();
1474 select_and_enable(&mut regs, 5, true);
1475 regs.write(REG_EVENT_SELECTOR, &6u32.to_be_bytes());
1476 assert_eq!(regs.read(REG_EVENT_NOTIFICATION, 4), vec![0, 0, 0, 0]);
1477 regs.write(REG_EVENT_SELECTOR, &5u32.to_be_bytes());
1478 assert_eq!(regs.read(REG_EVENT_NOTIFICATION, 4), vec![0, 0, 0, 1]);
1479 }
1480}