SDK
AO_TestDot
The Active Object that runs the E5 final test of a finished Kode Dot: the option, what it owns and asks, its verbs, the screens, the lock and the PASS.
What it is
The E5 station is the last test a Kode Dot goes through in the factory. The assembled unit sits in a nest, a computer drives it over the USB-C cable, a camera on the top connector watches the glass, and an operator presses buttons and touches the screen when the Kode Dot asks. AO_TestDot is the Active Object on the Kode Dot's side of that test.
test component=all is the test. The station's computer starts it and decides whether the unit passes. AO_TestDot owns the sequence: which row runs next, which photo is taken, how long each wait may last, and what the Kode Dot does with a PASS. It owns no device of the Kode Dot itself. Every row, button, brightness and charger setting belongs to its owner, and AO_TestDot asks that owner by event.
It sleeps from boot. At boot it reads one stored flag and nothing else: it claims nothing, allocates nothing and switches nothing on. The first verb from the cable that needs the station wakes it, and test_dot_end gives everything back. A customer's Kode Dot therefore carries the test and pays for it in flash and in a few KB of internal RAM.
The option is CONFIG_KODE_TEST_DOT. It is off in the SDK and on in kodeOS, as KODE_LOWPOWER and KODE_SCHEDULER are. An app that turns it on gets the same test. The wire between the computer and the Kode Dot is on internals/e5-wire-contract, and the rows it runs are on internals/production-test.
How it is connected
flowchart LR pc["Station computer<br/>USB-C console"] --> con["AO_Console"] con -- "TEST_DOT_REQ_SIG" --> td["AO_TestDot"] td -- "records, end, blobs" --> con td --> wk["its worker<br/>camera, relays, ADC, loop"] wk --> j3["J3: OV5640, PCF8574, ADS1115"] td -- "events" --> own["AO_Radio, AO_Autotest,<br/>AO_Buttons, AO_Touch, AO_Display,<br/>AO_Power, AO_Log, AO_Boot, AO_LedMgr"] td -- "TEST_DOT_SCREEN_REQ_SIG" --> host["AO_Gui (kgui)<br/>or kscreen"] host -- "test_dot_screen_done()" --> td class pc ext class con,td,own,host ao
What it owns and what it asks
While it is awake AO_TestDot holds the station's hardware on J3 and nothing else. Everything else is a request.
| Thing | How AO_TestDot gets it |
|---|---|
| J3 and its I2C controller | khal_exp_lend() for the 14 positions and khal_exp_ctrl() for the controller, both under the tag test_dot, taken by the camera's probe and returned by khal_exp_return() when the camera closes. The AO does not claim KBSP_RES_EXP, which the console holds for its exp verbs |
| J3's supply | exp_power_request() to AO_Radio, which switches it through the C5. The answer is EXP_POWER_SIG |
| The camera | kdrv_ov5640: an OV5640 over DVP, with its SCCB at 0x3C on J3's own I2C pair |
| The station's chips | kdrv_pcf8574 at 0x20 (four relays) and kdrv_ads1115 at 0x48 (four inputs), at 100 kHz on the same pair. The PCF8574 driver refuses the byte that closes R2 with R1 at rest, which would feed the computer's 5 V through the Kode Dot |
| The glass | display_brightness_request() to AO_Display: the level before the wake is read and put back at the end, 100 % during the test, 0 % for the photo of the panel off. A photo becomes a JPEG through display_jpeg_request(), because AO_Display owns the JPEG engine |
| The rows | autotest_run_request() to AO_Autotest as client AUTOTEST_CLIENT_TEST_DOT, one row at a time. AO_Autotest keeps each row and its budget |
| The operator's steps | buttons_test_request_from() and touch_test_request_from() with origin TEST_ORIGIN_TEST_DOT (3). A step of another client's test does not reach the run. AO_Buttons and AO_Touch write each step's record with buttons_test_record() and touch_test_record() (declared in kaos.h), the same text the console prints for its own test, and AO_TestDot puts it in the run unchanged. buttons_test_cancel_request() and touch_test_cancel_request() end them |
| The RGB LED | LED_RGB_SIG, published, for the camera's LED part |
| The pads' delivery test | power_boost_test_request() to AO_Power: the boost on with BOOST_LIM at 1.2 A, or off with the board's 0.5 A back |
| The dock console's pins | log_dock_lend_request() to AO_Log, which stops the dock's USB port and frees GPIO26 and GPIO27 for the pads loop |
| The factory reset of a PASS | boot_factory_reset_request(KBOOT_RESET_SHIP, ...) to AO_Boot |
| The screens | TEST_DOT_SCREEN_REQ_SIG, published to whichever LVGL owner the firmware has |
One worker task runs every job that blocks: the camera's probe, a grab, the analysis, the relays, the ADC, the pads loop. It runs one job and posts the result back, and AO_TestDot decides the next one (sdk/design-rules, rule 9). A job cannot be cancelled. One that runs past KODE_TEST_DOT_JOB_MS is given up, its step fails and its late answer is dropped, but it keeps the worker until it ends.
Dormant, awake, ending
AO_TestDot has one state and a mode.
| Mode | What holds | How it starts and ends |
|---|---|---|
| Dormant | Nothing. The lock flag is read | Boot, and the end of test_dot_end |
| Waking | The worker, J3's supply and the subscriptions, then the camera and the chips as the probe takes them | A verb that needs the station: test component=all, test_dot_place, test_dot_camera, test_dot_relays and test_dot_adc |
| Awake | All of that, and the memory the verb needs | The wake answered |
| Ending | Giving back, step by step | test_dot_end, or a wake that failed |
The other verbs wake nothing and run in any mode.
The wake and the refusals of test component=all
The wake is also the fixture check. It runs in this order, and the first step that fails ends it:
- The worker starts and the AO subscribes to the events it waits for.
- AO_Radio switches J3's supply on. The wait is
KODE_TEST_DOT_RAIL_MS(45 s), because a Wi-Fi sweep can hold the C5 that long. - The worker probes the camera on both sides of J3 for
KODE_TEST_DOT_PROBE_MS(2 s), then reads the module's version. A rail that did not come up or a camera that did not answer ends the wake withcamera. - The worker checks that the PCF8574 and the ADS1115 both answer and writes the relays' rest byte,
0xFF. A missing chip ends the wake withstation_i2c. - AO_Display is asked for the level it has, then for 100 %.
A wake that fails writes the relays' rest byte if the chips were touched, closes the camera, returns J3, switches the supply off and answers >err code=4 msg=<word>. The brightness is never touched, so the glass stays as it was.
test component=all is refused in this order. The first that applies answers:
| Order | Answer | Meaning |
|---|---|---|
| 1 | >err code=5 msg=not_built | The build has no CONFIG_KODE_TEST_DOT |
| 2 | >err code=5 msg=cable, net or sched | The line came from Bluetooth (cable), the Kode channel (net) or the scheduler (sched). Only the cable and the charging base may run it |
| 3 | >err code=5 msg=locked | The Kode Dot carries the station lock |
| 4 | >err code=4 msg=busy | A verb, an end or a run is in progress |
| 5 | >err code=4 msg=camera | J3's supply or the camera did not answer |
| 6 | >err code=4 msg=station_i2c | The relay expander or the ADC did not answer |
On a customer's Kode Dot, which has no fixture, test ends at step 5 after the wake's wait and leaves the Kode Dot as it found it.
The run
A run starts on the line of test component=all and keeps that line open until the camera part ends. It then closes the line with >ok and goes on in the background. What the background says goes into a ring of 64 records, which test_dot_poll drains.
| Part | What happens | Where its records go |
|---|---|---|
| Start | The automatic screen: a grid of 17 cells, the first for the identification and twelve for the rows | |
| Rows before the camera | sd, then imu while the Kode Dot is still. No card, or a card row that fails, ends the run (sd_missing) | The line |
| Camera part | The marks, the panel off, white, grey, red, green and blue, the verdicts, then the LED: off, red, green, blue and its verdict. Each step puts a pattern on the glass and waits for the host's answer before the photo | The line |
| Operator part | The six buttons, then the two touch targets, each with its own screen | The ring |
| Rows after | audio, haptic, mag, pmic, gauge, ioexp, clocks, ir, nfc, rfid, mem, beside the operator | The ring |
| End | timing=all, then poll state=done | The ring |
The run is over when the rows after the camera and the operator part have both ended. A watchdog of KODE_TEST_DOT_RUN_MS (240 s) ends it earlier. c5 and psram are not rows of the run. The computer's radio check covers the C5, and psram stays a row of its own.
The camera part is a fixed sequence. Each of its steps takes one photo or one verdict, and one QP rest paces the settle before a photo. The analysis is pure C in kaos/test_dot/analysis (tdan.h), with no hardware and no RTOS, and the host suite checks it against drawn frames.
The screens and the patterns
AO_TestDot draws nothing. It publishes TEST_DOT_SCREEN_REQ_SIG with a kind, a sequence number and a few argument bytes, and the firmware's LVGL owner builds it with the kui_test_dot module (sdk/kui). In kodeOS the host is AO_Gui, in its state Gui_test_dot. In an app the option selects kscreen, whose kscreen_test_dot_event() does the same on the app's glass.
The host draws the frame, flushes it, and only then calls test_dot_screen_done(kind, seq). AO_TestDot accepts an answer only when both the kind and the number match the request it waits for, so a late answer to an earlier request cannot release a later photo. The wait is KODE_TEST_DOT_SCREEN_MS (2 s). When it runs out the step goes on without a frame: a camera step fails, and the test_dot_screen verb answers ok=0.
| Kind | Shown | By |
|---|---|---|
Screen connecting | The connectors going in | test_dot_screen phase=connecting |
Screen auto | Hands off, with the grid of the rows. A new grid over an old one repaints only the cells that changed | The start of a run, and each row |
Screen led | A black glass, while the camera looks at the LED | The camera part |
Screens buttons, touch | The key or the target asked for, blinking, and the ones done | The operator part |
Screen result | PASS or FAIL over the whole glass | test_dot_result, and a run that aborts |
| Patterns | marks, white, grey 128, red, green, blue, and off to take a pattern away | The camera part |
| End | No screen: the host leaves the glass | test_dot_end |
The patterns keep the geometry the analysis reads, to the pixel, and none of them follows the kit's palette. The OK and FAIL icons exist as patterns and no run photographs them. The pads screen exists in the module, and no verb asks for it.
The result and the PASS
The computer decides the verdict. It tells the Kode Dot with test_dot_result.
verdict=passis refused withno_rununless a run reached its end since the lasttest_dot_end, and withfailedafter a FAIL. It then asks AO_Boot for the factory reset with the ship end and waits. When the reset is done the glass shows PASS and the verb answers>ok.- A reset that stops puts FAIL on the glass and answers
>err code=4 msg=reset_<step>, where the step isfind,install,apps,link,wipe,nvsorrequest. A reset that does not answer withinKODE_TEST_DOT_RESET_MSanswers>err code=6 msg=reset_timeout. verdict=failerases nothing. During a run it first ends the run (test=all result=aborted why=verdict), cancels the operator's steps and puts FAIL on the glass. The FAIL stays there untiltest_dot_endor the next boot.
The ship end of the reset does not restart the board. When every step is done, AO_Boot asks AO_Power to watch the cable. AO_Power counts KODE_SHIP_UNPLUG_MS (15 s) from the poll that sees VBUS gone. A poll that sees it back cancels the count and leaves AO_Power armed, and the next unplug counts the whole 15 s again. AO_Boot waits in Boot_shipping, awake and already erased, through all of it. When the count ends AO_Boot erases the NVS namespaces once more, because the settings keep being written while the cable is in. It then writes the boot request of the welcome app, the last thing it writes, and AO_Power opens the battery path. The glass shows PASS all that time. The wake from ship mode is BOT or USB, and the first boot after it runs the welcome app once.
AO_Boot walks the reset one step at a time, each with its own budget: find the welcome app, install it from the card if it is only there, remove the other apps, bring the C5 link up, wipe the C5's networks and keys and this chip's bonds, erase the other nvs namespaces, and write the request. It keeps the identity, the station lock, kodeOS, the welcome app and the files. A welcome app that is neither installed nor on the card stops it at find before anything is erased.
The lock
test_dot_lock confirm=on writes locked in the NVS namespace kstation (KODE_STATION_NVS_NS) and answers >ok. A failed write answers >err code=4 msg=nvs.
- In the session that locked, every verb except
test_dot_endanswers>err code=5 msg=locked. - At the next boot AO_TestDot reads the flag and every verb answers
locked,test_dot_endandtest_dot_infoincluded. - The factory reset keeps the key. Only a full flash erase (
esptool erase-flash) removes it, which is also how a returned unit is tested again. factory_reseton the console is not a station verb and still works.
The end
test_dot_end on a dormant Kode Dot has nothing to give back except the glass: it asks the screen host to leave it (TEST_DOT_SCR_END) and answers. A screen that test_dot_screen showed before any wake goes with it. On an awake one it gives back in this order: the run is cancelled and the LED goes off, the boost goes off, a job still out answers, the dock console's pins come back, the relays go to rest and the camera closes, then the supply, the brightness, the glass, the memory and the subscriptions. Every wait of the end has a bound, and together they are at most 50 s (KODE_TEST_DOT_END_MS). A job that does not answer in KODE_TEST_DOT_JOB_MS answers the verb with timeout, and the rest of the end runs in the background when the job does answer. Every verb is busy until then.
The end does not cancel the ship countdown. The boost limit is restored by the end, at the fall of VBUS and at every boot.
Specifications
TBD: not yet run on a Kode Dot on a station. The figures below come from the code and its host tests; the stack, the camera's timing and the pads loop need a run on the bench.
The option
| Thing | Value |
|---|---|
| Kconfig | KODE_TEST_DOT, in kaos/Kconfig. Default n in the SDK; y in kodeOS's sdkconfig.defaults |
| Needs | KODEOS_CONSOLE, KODEOS_RADIO and KODEOS_MIRROR, checked by an #error in test_dot_ao.c. It has no depends on, because kdrv's manifest reads the option |
| Project defaults | CONFIG_CAMERA_OV5640=y, CONFIG_CAMERA_OV5640_DVP_RGB565_BE_800X600_10FPS=y and CONFIG_CAMERA_OV5640_DVP_DEFAULT_FMT_RGB565_BE_800X600_10FPS=y. Without them kdrv_ov5640.c stops on an #error |
| Components it adds | esp_cam_sensor ==2.6.0 and esp_sccb_intf ==0.0.9, fetched only with the option on |
| Off | test component=all answers >err code=5 msg=not_built, no test_dot_* verb exists (code 1) and neither the camera driver nor its components are linked |
| Level | 1, KODE_PRIO_TEST_DOT, the level of KODE_PRIO_USER_LOW. With the option on KODE_PRIO_USER_LOW is not defined |
| Start | kaos_testdot_start(), a row of kode_init() after the console and before AO_Power, in either boot role |
| Layer rule | esp_cam_sensor, esp_sccb_*, esp_cam_ctlr* and ov5640.h only in kdrv_ov5640.c (tools/check_layers.sh, rule 10b) |
The task and its memory
| Thing | Value |
|---|---|
| Queue | 32 events (KODE_TEST_DOT_QUEUE_LEN) |
| Stack | 4096 bytes (KODE_TEST_DOT_STACK, the medium tier), static, internal RAM. The AO writes the lock to NVS, so it is not in PSRAM |
| Worker | One task at level 1 with a 16 KB stack in PSRAM (KODE_TEST_DOT_WORKER_STACK), holding the running job and 2 more |
| Records | At most 192 bytes each (KODE_FUNCTION_REC_MAX), formatted in place in a PSRAM FIFO of 96 entries and posted to the console from there. One that the console's queue or the event pool refuses waits and is posted again after 20 ms. The three radio records at their widest are asserted to fit; a record that is cut anyway logs record cut at N bytes (M wanted) at error level |
| Analysis planes | 4 planes of 1,672,800 bytes, PSRAM, taken by the first verb that needs the camera |
| Photos | Five 8-bit planes and two RGB565 photos of the 1056 x 768 window, a pool of 14 kept photos of 240,000 bytes, a blob pool of 512 KB, a JPEG buffer of 128 KB |
| Camera frames | Three buffers of at least 1056 x 768 x 2 bytes while the camera is open |
| Poll ring | 64 records of 192 bytes, taken at the start of a run |
| Total in PSRAM | About 23 MB with the camera open. test_dot_end frees all of it |
The verbs
Every verb is the cable's alone and is registered by kaos_test_dot_register(). Each one posts a TestDotReqEvt to AO_TestDot with the console's sequence number, and the AO answers with TEST_DOT_REC_SIG records, optional TEST_DOT_LEND_SIG blobs and one TEST_DOT_ANS_SIG. The console's watchdog for these verbs is KODE_TEST_DOT_LINE_MS (295.15 s). The fields of every record are on internals/e5-wire-contract.
| Verb | Keys | What it does and answers |
|---|---|---|
test component=all | none | The run. Rows, camera and LED on the open line, >ok at the camera's end. Wakes |
test_dot_place | win="x,y", cam_ms, mark_ms, dx_mm, deg | Looks for the four marks until the screen sits inside the limits, place records, the location blob, >ok. Wakes |
test_dot_poll | none | The ring, poll dropped= if any went, then poll state= lane= |
test_dot_screen | phase=connecting|result, verdict=pass|fail | Puts a screen of the station's own on the glass, screen phase= ok= |
test_dot_photos | only=1..255, a photo's number from 1 | The kept photos as blobs named photo_01_... upward, photos total= first (not with only) and photos n= bytes= ms= last |
test_dot_blob | name="location"|"colours" | One blob of the last pass again. no_blob when it was not kept |
test_dot_relays | state | Writes the relay byte, relays state= ok=. Wakes |
test_dot_adc | ch=0..3 | One conversion, adc ch= mv=. Wakes |
test_dot_loop | count=1..100, default 20 | Frames round the dock's pads at 9600 and 115200 baud and back, loop |
test_dot_radio | ssid24, ch24, ssid5, ch5, ble, discover | The C5 card, two Wi-Fi networks and a BLE name: radio_card, radio_time and radio |
test_dot_boost | state=on|off | The pads delivery test's boost, boost on= fault= current_ma= |
test_dot_result | verdict=pass|fail | The verdict. A pass runs the factory reset |
test_dot_lock | confirm=on | Stores the lock |
test_dot_end | none | Gives everything back |
test_dot_info | none | test_dot state= locked= run= psram_free= j3= |
test_dot_camera | only=framing|pixels|lines|zones|colour|led | The camera part alone, or one of its parts. Wakes |
The timeouts
Every wait of the AO has a bound, held in kqp_config.h.
| Wait | Bound |
|---|---|
| J3's supply | 45 s (KODE_TEST_DOT_RAIL_MS) |
| The camera's probe | 2 s (KODE_TEST_DOT_PROBE_MS) |
| A worker job | 20 s (KODE_TEST_DOT_JOB_MS) |
| A JPEG from AO_Display | 2 s (KODE_TEST_DOT_JPEG_MS) |
| A screen from its host | 2 s (KODE_TEST_DOT_SCREEN_MS) |
| The boost's answer, the dock's pins | 5 s (KODE_TEST_DOT_ANSWER_MS) |
| The radio check | 33 s (KODE_TEST_DOT_RADIO_MS): KODE_RADIO_PROBE_MS (8 s) plus 25 s behind a running join |
| A discovery sweep | 148 s (KODE_TEST_DOT_DISCOVER_MS): the radio check's bound, one whole Wi-Fi sweep (102 s, KODE_TEST_DOT_SWEEP_MS), the probe budget again and 5 s of margin |
| The pads loop | 30 s (KODE_TEST_DOT_LOOP_MS): the dock's pins lent, the frames' job, the pins back |
| The end | 50 s (KODE_TEST_DOT_END_MS): the boost off, a job still out, the dock's pins back, the close job |
| The reset of a PASS | 290.15 s (KODE_TEST_DOT_RESET_MS is KODE_RESET_CONSOLE_MS, the sum of the reset's steps) |
| The console's line | 295.15 s (KODE_TEST_DOT_LINE_MS): the reset plus a margin of 5 s |
| A whole run | 240 s (KODE_TEST_DOT_RUN_MS) |
| A lent blob coming back | 60 s (KODE_TEST_DOT_BLOB_MS) |
| The operator's screen before the first prompt | 150 ms |
The bounds stack. Each one sits above the one it contains: the Kode Dot's own bound, then the console's line, then the wait of the computer, which adds 5 s to the bound (internals/e5-wire-contract). The AO's answer, a timeout included, therefore always reaches the line before anything gives it up. _Static_asserts in test_dot_ao.c hold the order: the sweep covers the driver's two scan constants, a discovery is longer than a radio check, and the line is longer than the reset, a discovery, the pads loop, the end, and the fixture check and a run's watchdog together.
A run that hits its watchdog prints test=all result=timeout why=watchdog, ends the line with >err code=6 msg=test if it is still open and shows FAIL.
The signals and the calls
| Name | Direction | What it is |
|---|---|---|
TEST_DOT_REQ_SIG | in, posted | A verb from the console (TestDotReqEvt) |
TEST_DOT_REC_SIG, TEST_DOT_ANS_SIG, TEST_DOT_LEND_SIG | out, posted to the asker | A record, the end, a blob lent for the wire. Each carries the console's sequence number, and the console drops one that is not the number of its command |
TEST_DOT_BACK_SIG | in, posted | The console has written the lent blob and reads it no more |
TEST_DOT_SCREEN_REQ_SIG | out, published | A screen or a pattern for the LVGL owner (TestDotScreenEvt: kind, seq, up to 18 bytes of arguments) |
TEST_DOT_SCREEN_DONE_SIG | in, posted | The host shows it (TestDotScreenDoneEvt: kind, seq) |
test_dot_screen_done(kind, seq) | call | What the host calls once the frame is on the panel |
test_dot_run_request() | call | The console hands test component=all over |
test_dot_busy() | call | True from the start of a run to its end. The console refuses factory_reset as busy while it is |
test_dot_blob_back() | call | The console gives a blob back |
A blob is lent, never copied. A copy of tens of kilobytes into events would be the pool's whole size, so the console writes the bytes from the AO's memory, paced like help, and gives the loan back when the last line is out.
Gotchas
Example code
You can see the dormant AO on any Kode Dot built with the option, with no station. Connect the cable and use tools/kcli.py.
- Run
python3 tools/kcli.py -p PORT --settle 5 cmd 'test_dot_info'. It prints>|test_dot state=dormant locked=0 run=none psram_free=<bytes> j3=freeand>ok. - Run
python3 tools/kcli.py -p PORT --settle 5 cmd --timeout 60 'test component=all'. After a pause it prints>err code=4 msg=camera. - Run
test_dot_infoagain. The state is stilldormant,j3isfreeandpsram_freeis the figure of step 1.
In the SDK
components/kaos/test_dot:test_dot_ao.c(the AO),test_dot_verbs.c(the console's side),test_dot_cam.candtest_dot_worker.c(the worker's jobs), andanalysis/(tdan.h, the screen analysis).components/kdrv:ov5640/(the camera),station/(kdrv_pcf8574,kdrv_ads1115andkdrv_dock_loop).components/kui/test_dot: the screens and patterns.kqp_config.h: every bound above.