Sidereal (LST) and apparent-solar time-row modes#8
Draft
peterlewis wants to merge 12 commits into
Draft
Conversation
2f18d8e to
bc392f7
Compare
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 6, 2026
The exact residue between the pure union of the five draft PRs (mitxela#5 $PMTXTS, mitxela#6 astro pack, mitxela#7 hardening, mitxela#8 sidereal/solar, mitxela#9 tempcomp + significance-fade + tc_seed — mitxela#5 and mitxela#6 arrive as ancestors of mitxela#9 and mitxela#8) and the rollup tree that has been emulator-verified and hardware-run: - astro.c/astro.h/test_astro.c: rollup astro refinements not yet folded back into the refreshed PR mitxela#6/mitxela#8 heads (candidates for a future PR update) - version.c: rollup version string - qspi/output/*.bin: rollup's built firmware images - .settings IDE noise After this commit the megapack tree is BYTE-IDENTICAL to the previously validated rollup (feaf970) while the merge ancestry proves containment of all five PR heads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 7, 2026
…nts into the rollup)
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 7, 2026
…head) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
§0 prerequisites (behaviour-neutral, unblock everything downstream): - MItem.lo/hi/step int16_t -> int32_t (main.h) — page_ms hi=60000 and matrix hi=100000 overflow a signed 16-bit field, which is the REAL reason PAGE MS was stuck at its floor (the up-tap clamp wrapped to -5536). menu_val is already i32. - ovr.simple_mask + cfg_simple_defined uint8_t -> uint16_t, for KID_MATRIX_FREQ=8 / KID_TEMPCOMP=9 (1u<<8 overflows a u8). Persisted simple_mask now 2 bytes at r[14..15]; byte 15 was zeroed padding in every prior record, so old records round-trip and EE_SCHEMA stays 1 (bumping it would wipe stored settings on OTA). - KID enum: append KID_MATRIX_FREQ=8, KID_TEMPCOMP=9; rename KID_ALT_COLON -> KID_COLON_ALT (value stays 3 — the persisted bit/byte are unchanged, cosmetic). §3a PAGE MS: g_page now returns the EFFECTIVE page_ms() (5500 default, 250 floor) instead of the raw config.page_ms sentinel (0), and the row floor is 250 — so the editor shows 5500 and steps, instead of a misleading 0 that wouldn't increase. (Held for the sidereal-branch cascade: the alt_colon_mode -> colon_alt_mode config KEY rename; that's owned by PR mitxela#8, not the menu PR.) All emu suites green; ARM 81%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
Renames the alt-colon control from ALTCOLON to COLONALT (reads as a COLON variant, groups with COLON in DISP) and the config key alt_colon_mode -> colon_alt_mode. Clean rename, no alias (draft PR — no compat burden). The stable KID_COLON_ALT id (3) is unchanged, so persisted overrides survive. NOTE / cascade: the config KEY originated in the sidereal PR (mitxela#8, bc392f7); rollup is merge-based, so when PR mitxela#8 is refreshed this key rename must be back-ported onto sidereal-modes and re-merged. Applied here so the integrated rollup firmware is self-consistent and flashable now. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
The date-row countdown was <name> H:MM — the H-MM dash read ambiguously (is 0-01 an hour or a minute?). Show bare space-separated values instead: under an hour it's minutes seconds (<name> 1 35), an hour or more switches to hours-'h'-minutes (<name> 2h15) so a distant transit can't masquerade as 2 min 15 s and the field still fits the 10-char row. serial sentence unchanged. NOTE / cascade: MODE_STAR is the star-transit PR (onto the mitxela#8 base); this display tweak should be back-ported there when that PR is refreshed. Applied on rollup so it's flashable now. FORMAT is my read of 'm and s rather than dashes, just the values' — confirm the exact unit choice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With `pps = on` in config.txt, emit one proprietary NMEA sentence per PPS edge over the existing CDC stream, carrying the sub-second phase captured at the edge plus calibration / holdover / die-temperature telemetry. Time-critical fields are snapshotted in the PPS ISR; formatting and CDC submission happen in the main loop, serialised against the NMEA passthrough. With no enumerated host the record is dropped before any formatting. mk4-time/Core/Src/main.c: capturePPS(), emitPPSTimestamp(), measure_temp(), a `pps` config key and one main-loop hook. qspi/config.txt documents the key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ver USB Experimental attack on the ~6ms host-driven USB read jitter that swamps the clock's true ~180us precision — without a hardware PPS wire. The OTG_FS core rules out mitxela's last-microsecond FIFO injection, so instead we let the host anchor each PPS to a USB Start-Of-Frame, whose own arrival time the host can read in hardware (macOS IOKit GetBusFrameNumberWithTime). - Enable DWT->CYCCNT (free-running 12.5ns core-cycle counter) as the timebase both the PPS edge and each SOF are latched against. Unaffected by tempcomp SysTick steering (counts raw core clock), so it's a stable monotonic ruler. - Enable the USB SOF interrupt; PCD_SOFCallback latches (DWT, 11-bit frame from DSTS[13:8]) every 1ms, as its first act for minimal latency. - capturePPS() latches DWT at the edge; emitPPSTimestamp() appends the tail ,dwt_pps,sof_frame,dwt_sof to $PMTXTS (read under __disable_irq, no torn read). - Bump NMEA_BUF_SIZE 90->128 for the longer sentence. Host places the edge at hostTime(sof_frame) + (dwt_pps-dwt_sof)/f_dwt, immune to delivery lateness; dwt_pps deltas self-calibrate f_dwt (no core-clock assumption). Backward-compatible: lenient $PMTXTS parsers read the first 9 fields. Bench-only; +1kHz SOF ISR (~0.05% CPU) is the cost to watch. Builds clean (0 errors). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adversarial audit of the SOF-correlation change surfaced two real issues: - Stale first anchor: pps_sof_dwt/frame init to 0, so the first PPS emitted after enumeration (or with pps just toggled on, or on the emulator which has no USB SOF) carried a (0,0) anchor → a ~53s/−729ms host error on that record. Now a pps_sof_valid flag gates the tail: emit the plain 9-field sentence until a real SOF has latched. Backward-compatible; also makes the emulator correct. - SOF work ran unconditionally. Gate the latch on pps_ts_enabled (the SOF IRQ still fires — cheap, below the priority-0 display DMA — but does nothing when the feature is off); clear valid when off so a re-enable can't reuse a stale anchor. config.txt documents that pps=on enables the SOF tail. Builds clean (0 errors). Refuted findings (torn read, 16-bit atomicity, buffer truncation, display preemption, scope) left as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Five GPS-derived astronomy read-outs, each opt-in via a MODE_* config key and shown on the 10-char date row while the live clock keeps running on the time row (SATVIEW-style, COUNT_NORMAL): MODE_SUN sunrise / sunset / solar noon (local), auto-paged MODE_SUN_AZEL sun azimuth & elevation, now MODE_MOON moon phase index (0-7) + illuminated % MODE_GRID Maidenhead grid locator MODE_LATLON latitude / longitude, auto-paged The astronomy maths is a self-contained Core/Src/astro.c (+ astro.h): low- precision NOAA/Meeus sun alt-az and event times, moon phase, equation of time, and Maidenhead. It has no firmware dependencies, so it unit-tests natively (test/test_astro.c, 45 reference vectors) and was validated to ~1e-5 before touching the firmware. The L4 has only a single-precision FPU, so the double maths would be slow soft- float. It is therefore computed in the main loop (astro_update(), gated on the active mode exactly as measure_vbat() is for MODE_VBAT) and swapped into a cache under a brief __disable_irq() mask; the sendDate() cases that run inside the SysTick ISR only format the cached scalars -- no trig in the interrupt. Modes are disabled by default; with no GPS fix they use fake_latitude/longitude if set, else show "----". config.txt documents the keys and the moon-phase index legend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"LAT 51.48" vs "LAT-51.48" let the minus swallow the separator. Add a sign
slot after the label space — "LAT 51.48" / "LAT -51.48" — so the digits
start in the same column either way, matching the RISE/SET layout. A 3-digit
longitude can't fit both the separator and the slot in 10 characters, so the
separator alone is dropped there ("LON-179.99").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The paged modes (SUN, LATLON) previously flipped sub-screen every 2 s hard-coded. Make the dwell a config key (page_ms, ms; unset -> 5500, floored at 250 so a tiny value can't flood the date-board UART), driven off uwTick, and repaint the moment a page flips rather than waiting for the 1 Hz date-row refresh (guarded by decisec!=9 to avoid racing the SysTick sendDate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
§0 prerequisites (behaviour-neutral, unblock everything downstream): - MItem.lo/hi/step int16_t -> int32_t (main.h) — page_ms hi=60000 and matrix hi=100000 overflow a signed 16-bit field, which is the REAL reason PAGE MS was stuck at its floor (the up-tap clamp wrapped to -5536). menu_val is already i32. - ovr.simple_mask + cfg_simple_defined uint8_t -> uint16_t, for KID_MATRIX_FREQ=8 / KID_TEMPCOMP=9 (1u<<8 overflows a u8). Persisted simple_mask now 2 bytes at r[14..15]; byte 15 was zeroed padding in every prior record, so old records round-trip and EE_SCHEMA stays 1 (bumping it would wipe stored settings on OTA). - KID enum: append KID_MATRIX_FREQ=8, KID_TEMPCOMP=9; rename KID_ALT_COLON -> KID_COLON_ALT (value stays 3 — the persisted bit/byte are unchanged, cosmetic). §3a PAGE MS: g_page now returns the EFFECTIVE page_ms() (5500 default, 250 floor) instead of the raw config.page_ms sentinel (0), and the row floor is 250 — so the editor shows 5500 and steps, instead of a misleading 0 that wouldn't increase. (Held for the sidereal-branch cascade: the alt_colon_mode -> colon_alt_mode config KEY rename; that's owned by PR mitxela#8, not the menu PR.) All emu suites green; ARM 81%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
16ece45 to
36308a8
Compare
- loadRules: reject untrusted rowLength/numEntries from TZRULES.BIN (RAM overflow) - MODE_TEXT: clamp snprintf untruncated-return index (uart2_tx_buffer OOB) - mk4-date latchDisplay: bound dp_pos per orientation (buffer_a/b OOB) - readConfigFile: don't treat a zero FAT timestamp as a cache hit (first-boot hang) - config-over-USB: defer postConfigCleanup out of the USB ISR (sendDate/nextMode races) - firmwareCheckOnEject: fatfs_busy guard for FATFS reentrancy on USB eject Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two further pre-existing bugs from the same read-through. - mk4-time NMEA-over-CDC: CDC_Copy_Transmit() / CDC_Transmit_FS() dereference hUsbDeviceFS.pClassDataCDC unconditionally, but it is NULL until a USB host enumerates. The default nmea_cdc_level = NMEA_ALL forwards every GPS sentence to CDC, so on charger-only power the first forwarded sentence faults in ISR context. NULL guard on both, plus a length clamp before the memcpy. - mk4-date CMD_LOAD_TEXT: 'if (text_idx > MAX_TEXT_LEN) return;' passes at text_idx == MAX_TEXT_LEN and writes text[32], one past the 32-byte array. '>' -> '>='. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
36308a8 to
c9b69bb
Compare
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
§0 prerequisites (behaviour-neutral, unblock everything downstream): - MItem.lo/hi/step int16_t -> int32_t (main.h) — page_ms hi=60000 and matrix hi=100000 overflow a signed 16-bit field, which is the REAL reason PAGE MS was stuck at its floor (the up-tap clamp wrapped to -5536). menu_val is already i32. - ovr.simple_mask + cfg_simple_defined uint8_t -> uint16_t, for KID_MATRIX_FREQ=8 / KID_TEMPCOMP=9 (1u<<8 overflows a u8). Persisted simple_mask now 2 bytes at r[14..15]; byte 15 was zeroed padding in every prior record, so old records round-trip and EE_SCHEMA stays 1 (bumping it would wipe stored settings on OTA). - KID enum: append KID_MATRIX_FREQ=8, KID_TEMPCOMP=9; rename KID_ALT_COLON -> KID_COLON_ALT (value stays 3 — the persisted bit/byte are unchanged, cosmetic). §3a PAGE MS: g_page now returns the EFFECTIVE page_ms() (5500 default, 250 floor) instead of the raw config.page_ms sentinel (0), and the row floor is 250 — so the editor shows 5500 and steps, instead of a misleading 0 that wouldn't increase. (Held for the sidereal-branch cascade: the alt_colon_mode -> colon_alt_mode config KEY rename; that's owned by PR mitxela#8, not the menu PR.) All emu suites green; ARM 81%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
§0 prerequisites (behaviour-neutral, unblock everything downstream): - MItem.lo/hi/step int16_t -> int32_t (main.h) — page_ms hi=60000 and matrix hi=100000 overflow a signed 16-bit field, which is the REAL reason PAGE MS was stuck at its floor (the up-tap clamp wrapped to -5536). menu_val is already i32. - ovr.simple_mask + cfg_simple_defined uint8_t -> uint16_t, for KID_MATRIX_FREQ=8 / KID_TEMPCOMP=9 (1u<<8 overflows a u8). Persisted simple_mask now 2 bytes at r[14..15]; byte 15 was zeroed padding in every prior record, so old records round-trip and EE_SCHEMA stays 1 (bumping it would wipe stored settings on OTA). - KID enum: append KID_MATRIX_FREQ=8, KID_TEMPCOMP=9; rename KID_ALT_COLON -> KID_COLON_ALT (value stays 3 — the persisted bit/byte are unchanged, cosmetic). §3a PAGE MS: g_page now returns the EFFECTIVE page_ms() (5500 default, 250 floor) instead of the raw config.page_ms sentinel (0), and the row floor is 250 — so the editor shows 5500 and steps, instead of a misleading 0 that wouldn't increase. (Held for the sidereal-branch cascade: the alt_colon_mode -> colon_alt_mode config KEY rename; that's owned by PR mitxela#8, not the menu PR.) All emu suites green; ARM 81%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-loop stall $PMLOOP on real hardware pinned the once-per-second main-loop stall to tag 3 (the timezone lookup): a steady $PMLOOP,300,3 every second on a STATIONARY, GPS-locked clock. Root cause: the NMEA parser sets new_position on every 1 Hz fix, and the main loop answers it by re-running the full ZoneDetect lookup — f_open TZMAP.BIN → ZDOpenDatabase → polygon search, all off the QSPI FATFS, ~300 ms. With no distance threshold, metre-scale GPS jitter re-ran the whole thing every second: the clock re-decided it was in Europe/London 60×/minute. That 300 ms stall is what starved the seg-balance mirror refill (the once-per-second BALANCE freeze — the ISR-fresh-mirrors fix cured the symptom, this cures the cause) and is the dominant contributor to the date-board OVRDIS byte-drop window. Fix: gate the lookup on real movement — skip unless the fix moved >0.005° (≈0.5 km) since the last lookup. ~100× the jitter of a still clock (so it looks up exactly ONCE), far finer than any timezone boundary (so a moving clock still re-detects within ~0.5 km of a crossing). The per-loop distance check is a handful of FP ops. Device-main-loop only — inert in the emulator (which never runs main()'s while(1)); emu + ARM both build clean, date board unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MODE_LST and MODE_SOLAR turn the big HH:MM:SS digits into a live ticking Local Sidereal Time or apparent-solar clock. Both reuse the MODE_COUNTDOWN takeover pattern: the heavy GMST/EoT double maths runs once per second in the main loop, staging pre-rendered digits that the SysTick handlers latch at the true GPS PPS boundary -- so accuracy and the P3..P0 sub-second digit-hiding are inherited from the disciplined civil second, and civil timekeeping (currentTime, PPS, the date row) is untouched. The display is quantised to civil second boundaries and reseeded every second, so sidereal's 1.00273791x rate makes the seconds double-step once every ~6 min -- the honest signature of a GPS-disciplined sidereal clock. A dedicated colon animation (alt_colon_mode, default alt_sawtooth, kept distinct from the civil colon) marks the mode so it can never be mistaken for civil time; apparent solar especially can sit within minutes of it. With no position the row shows dashes -- never GMST-as-LST. astro.c regains local_sidereal_time()/local_solar_time() with the GMST series factored into one helper (gmst_hours, +T^2 term); native suite 53/53 incl. the IAU J2000 anchor and Meeus example 12.b. All default-off: stock behaviour unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sun_subsolar(t, &lat, &lon) returns the point where the sun is at the zenith: latitude = solar declination, longitude where the local hour angle is zero (lon = alpha - gmst*15). It reuses the same gmst_hours series as sun_az_el and local_sidereal_time, so the three can never drift apart. Library + tests only — no display mode consumes it yet (a companion app plots it). Tests: the sun must sit at elevation 90 deg over its own subsolar point at every existing test instant (self-consistent but not circular — az/el and subsolar take different paths to the sky), plus declination anchors at the solstices and the absolute GMST anchors (IAU J2000, Meeus ex. 12.b) recorded in comments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
c9b69bb to
f998de6
Compare
peterlewis
added a commit
to peterlewis/clock4
that referenced
this pull request
Jul 12, 2026
§0 prerequisites (behaviour-neutral, unblock everything downstream): - MItem.lo/hi/step int16_t -> int32_t (main.h) — page_ms hi=60000 and matrix hi=100000 overflow a signed 16-bit field, which is the REAL reason PAGE MS was stuck at its floor (the up-tap clamp wrapped to -5536). menu_val is already i32. - ovr.simple_mask + cfg_simple_defined uint8_t -> uint16_t, for KID_MATRIX_FREQ=8 / KID_TEMPCOMP=9 (1u<<8 overflows a u8). Persisted simple_mask now 2 bytes at r[14..15]; byte 15 was zeroed padding in every prior record, so old records round-trip and EE_SCHEMA stays 1 (bumping it would wipe stored settings on OTA). - KID enum: append KID_MATRIX_FREQ=8, KID_TEMPCOMP=9; rename KID_ALT_COLON -> KID_COLON_ALT (value stays 3 — the persisted bit/byte are unchanged, cosmetic). §3a PAGE MS: g_page now returns the EFFECTIVE page_ms() (5500 default, 250 floor) instead of the raw config.page_ms sentinel (0), and the row floor is 250 — so the editor shows 5500 and steps, instead of a misleading 0 that wouldn't increase. (Held for the sidereal-branch cascade: the alt_colon_mode -> colon_alt_mode config KEY rename; that's owned by PR mitxela#8, not the menu PR.) All emu suites green; ARM 81%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Adds
MODE_LSTandMODE_SOLAR: the time row ticks Local Sidereal Time (with the authentic ~6-minute double-step) or sundial time, reseeded each second from the GPS-disciplined clock, with acolon_alt_modeanimation kept automatically distinct from civil time.Stack: based on
firmware-hardening(#7).Try it now: fwt.bin · fwd.bin — the union of this PR stack, byte-identical to the merged result.
Docs:
docs/firmware-additions.md, shipped in the final (menu) PR.Note
You can try this PR without hardware: the PCC web app runs the real clock4 firmware compiled to WebAssembly, and its clock is built from a rollup of the whole PR series together, so this PR's behaviour is live there alongside the others. The app's DEVICE → UPDATES panel shows the exact commit it was built from. Fair warning: the app around the firmware is beta and still has bugs — the firmware behaviour is the real thing, the app chrome less so.
Draft — two new opt-in time-row modes that turn the big HH:MM:SS digits into a live ticking clock in an alternate timebase:
MODE_LST— Local Sidereal Time (a GPS-disciplined sidereal clock, for the astronomers).MODE_SOLAR— local apparent solar time; reads exactly 12:00:00 at your meridian transit.Stacking note (please read first)
The series is a linear stack; this PR is based on #7 (firmware-hardening), so the GitHub diff vs
mastershows the lower PRs' commits too. The sidereal-specific changes are the branch's own commits — the two modes, thecolon_alt_modeconfig-key rename, and the subsolar-point helper — those are the ones to review (the branch's diff against #7's head). Merging the stack in order makes each step a fast-forward; happy to reorder or split further if you'd prefer.How it stays honest and leaves civil timekeeping untouched
It clones the existing
MODE_COUNTDOWNtakeover (newCOUNT_ALT, fourSysTick_Alt_Pxclones). The GMST/EoT double maths runs once per second in the main loop; the SysTick handlers latch pre-rendered digits at the true PPS boundary via the untouchedPPS()path. So civilcurrentTime, PPS discipline,calibrateRTC/write_rtcand the date row are byte-identical to stock; thesetPrecisionP3→P0 sub-second digit-hiding applies to sidereal unchanged; and no double-precision maths ever runs in an ISR.The display is quantised to civil second boundaries and reseeded each second, so sidereal's 1.00273791× rate makes the seconds display double-step about once every 6 minutes — deliberately visible, the honest signature of a GPS-disciplined sidereal clock rather than a rate-warped one. A dedicated colon animation (
colon_alt_mode, defaultalt_sawtooth, kept distinct from the civil colon) marks the mode so it can't be mistaken for civil time; with no position the row shows dashes rather than GMST-as-LST.Maths
astro.cregainslocal_sidereal_time()/local_solar_time()with the GMST series factored into onegmst_hours()helper (+T² term for sub-ms accuracy for decades). Native suite 53/53, including the IAU GMST(J2000.0) anchor and Meeus example 12.b. DUT1 (±0.9 s) is the documented accuracy floor.Default-off: without the config keys, no alt handler is installed and behaviour is identical to stock. Builds clean (
-O3, no new warnings); passed an adversarial review pass. Hardware validation still pending — hence draft.