{ "_description": "S7-1500 profile — DB1 (1024 bytes) + MB (256 bytes) + TM/CT (64 bytes each) with well-known seeds at named offsets for the smoke + byte-order + string + Timer/Counter tests. Big-endian Siemens wire order throughout.", "areas": [ { "area": "DB", "index": 1, "size": 1024, "seeds": [ { "_desc": "DB1.DBW0 — read-back probe, S7Driver default ProbeAddress target is MW0; this shadows it", "offset": 0, "type": "u16", "value": 4242 }, { "_desc": "DB1.DBW10 — i16 smoke value for SmokeI16 read path", "offset": 10, "type": "i16", "value": -12345 }, { "_desc": "DB1.DBD20 — i32 smoke value for SmokeI32 read path", "offset": 20, "type": "i32", "value": 1234567890 }, { "_desc": "DB1.DBD30 — f32 smoke value for SmokeF32 read path (IEEE-754 big-endian)", "offset": 30, "type": "f32", "value": 3.14159 }, { "_desc": "DB1.DBX50.3 — bool bit at byte-50 bit-3 for SmokeBool read path", "offset": 50, "type": "bool", "value": true, "bit": 3 }, { "_desc": "DB1.DBW100 — scratch for write-then-read round-trip tests; seeded 0", "offset": 100, "type": "u16", "value": 0 }, { "_desc": "DB1.STRING[200] — S7 string 'Hello' (max 32, cur 5)", "offset": 200, "type": "ascii", "value": "Hello", "max_len": 32 }, { "_desc": "DB1.DBW300 — span-planner gate (S7_1500SpanReadTests) starts here. Cluster A spans 300..320 and merges into ONE block read under the 16-byte gap budget. Every seed from 300 on is distinct and non-zero so a mis-sliced block can never accidentally match (the #536 zero-source trap).", "offset": 300, "type": "u16", "value": 51001 }, { "_desc": "DB1.DBW302 — cluster A, adjacent to DBW300 (gap 0)", "offset": 302, "type": "i16", "value": -21001 }, { "_desc": "DB1.DBD304 — cluster A, adjacent to DBW302 (gap 0)", "offset": 304, "type": "i32", "value": 305419896 }, { "_desc": "DB1 @308 — POISON in the 308..310 hole the span reads through; no tag maps here, so a mis-slice yields 0xDEAD rather than a zero that could pass by luck", "offset": 308, "type": "u16", "value": 57005 }, { "_desc": "DB1.DBX310.5 — cluster A bool (gap 2 from the DBD304 end)", "offset": 310, "type": "bool", "value": true, "bit": 5 }, { "_desc": "DB1 @311 — POISON in the 311..316 hole", "offset": 311, "type": "u8", "value": 90 }, { "_desc": "DB1 @312 — POISON in the 311..316 hole (0xDEADBEEF)", "offset": 312, "type": "u32", "value": 3735928559 }, { "_desc": "DB1.DBD316 — cluster A f32, gap 5 from the bool byte's end; seals the span at 300..320 = 20 bytes", "offset": 316, "type": "f32", "value": 2.7182817 }, { "_desc": "DB1.DBW400 — cluster B; 80 bytes past cluster A's end, so the 16-byte gap budget forces a SECOND span rather than one merged read", "offset": 400, "type": "u16", "value": 40201 }, { "_desc": "DB1.DBW402 — cluster B's second member; two members keep the span off the PrefersPerTagRead demotion", "offset": 402, "type": "u16", "value": 40202 }, { "_desc": "DB1.DBW500 — isolated narrow scalar: its own single-member span, demoted by PrefersPerTagRead into a leftover that rides the multi-var pass", "offset": 500, "type": "u16", "value": 60001 }, { "_desc": "DB1.DBW600 — second isolated narrow scalar, likewise a multi-var leftover", "offset": 600, "type": "i16", "value": -32001 } ] }, { "area": "MK", "index": 0, "size": 256, "seeds": [ { "_desc": "MW0 — probe target for S7ProbeOptions.ProbeAddress default", "offset": 0, "type": "u16", "value": 1 }, { "_desc": "MW10 — span-planner gate: a narrow scalar in a DIFFERENT area from the DB1 leftovers. A block read can never merge across (area, DB); the multi-var pass can, so this pins that the leftover batching crosses the boundary.", "offset": 10, "type": "u16", "value": 4097 } ] }, { "_desc": "TM (timers) — 64 bytes = 32 timer cells. A timer is a 2-byte S5TIME word and timer number N lives at byte offset 2*N, so the offsets below ARE the timer numbers doubled. Values are S5TIME-encoded (bits 12..13 = time base, the remaining three nibbles = a BCD 0..999 count) and each decodes to a distinct NON-ZERO duration, because a zero-valued timer reads the same as a timer that was never served at all (the #536 trap). ⚠️ Only T0 is readable through this fixture — see the CT note below and Docker/README.md §'Timer / Counter (TM / CT) areas'.", "area": "TM", "index": 0, "size": 64, "seeds": [ { "_desc": "T0 @ byte 0 — 0x2456: base 2 (×1s), BCD 456 ⇒ 456.0 seconds. Device 0 is the one cell where snap7's address resolution and a real CPU's agree, so this is the seed S7_1500SpanReadTests asserts on.", "offset": 0, "type": "u16", "value": 9302 }, { "_desc": "T5 @ byte 10 — 0x1123: base 1 (×0.1s), BCD 123 ⇒ 12.3 seconds. Reachable only from a real CPU (S7_TIMER_NUMBER_ADDRESSING=1).", "offset": 10, "type": "u16", "value": 4387 }, { "_desc": "T9 @ byte 18 — 0x0250: base 0 (×0.01s), BCD 250 ⇒ 2.5 seconds. Real CPU only, as above.", "offset": 18, "type": "u16", "value": 592 } ] }, { "_desc": "CT (counters) — 64 bytes = 32 counter cells, counter number N at byte offset 2*N. The driver surfaces a counter as the RAW big-endian word (S7Driver.DecodeScalarBlock via S7.Net's Counter.FromByteArray), so these seeds are plain u16 counts; distinct + non-zero for the same reason as the timers. ⚠️ snap7's server resolves the TM/CT request address as a BIT address (byte = addr>>3) instead of as a device NUMBER, while S7.Net (and a real CPU) put the device number there — so a read of C{n} lands on byte n>>3, and device 0 is the only n where the two conventions coincide. Live-probed 2026-08-17; see Docker/README.md.", "area": "CT", "index": 0, "size": 64, "seeds": [ { "_desc": "C0 @ byte 0 — count 4711. The device-0 cell both conventions agree on; asserted by S7_1500SpanReadTests.", "offset": 0, "type": "u16", "value": 4711 }, { "_desc": "C7 @ byte 14 — count 31337. Reachable only from a real CPU (S7_TIMER_NUMBER_ADDRESSING=1).", "offset": 14, "type": "u16", "value": 31337 }, { "_desc": "C11 @ byte 22 — count 1234. Real CPU only, as above.", "offset": 22, "type": "u16", "value": 1234 } ] } ] }