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
Who AO_TestDot talks to

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.

ThingHow AO_TestDot gets it
J3 and its I2C controllerkhal_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 supplyexp_power_request() to AO_Radio, which switches it through the C5. The answer is EXP_POWER_SIG
The camerakdrv_ov5640: an OV5640 over DVP, with its SCCB at 0x3C on J3's own I2C pair
The station's chipskdrv_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 glassdisplay_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 rowsautotest_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 stepsbuttons_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 LEDLED_RGB_SIG, published, for the camera's LED part
The pads' delivery testpower_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 pinslog_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 PASSboot_factory_reset_request(KBOOT_RESET_SHIP, ...) to AO_Boot
The screensTEST_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.

ModeWhat holdsHow it starts and ends
DormantNothing. The lock flag is readBoot, and the end of test_dot_end
WakingThe worker, J3's supply and the subscriptions, then the camera and the chips as the probe takes themA verb that needs the station: test component=all, test_dot_place, test_dot_camera, test_dot_relays and test_dot_adc
AwakeAll of that, and the memory the verb needsThe wake answered
EndingGiving back, step by steptest_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:

  1. The worker starts and the AO subscribes to the events it waits for.
  2. 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.
  3. 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 with camera.
  4. The worker checks that the PCF8574 and the ADS1115 both answer and writes the relays' rest byte, 0xFF. A missing chip ends the wake with station_i2c.
  5. 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:

OrderAnswerMeaning
1>err code=5 msg=not_builtThe build has no CONFIG_KODE_TEST_DOT
2>err code=5 msg=cable, net or schedThe 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=lockedThe Kode Dot carries the station lock
4>err code=4 msg=busyA verb, an end or a run is in progress
5>err code=4 msg=cameraJ3's supply or the camera did not answer
6>err code=4 msg=station_i2cThe 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.

PartWhat happensWhere its records go
StartThe automatic screen: a grid of 17 cells, the first for the identification and twelve for the rows
Rows before the camerasd, then imu while the Kode Dot is still. No card, or a card row that fails, ends the run (sd_missing)The line
Camera partThe 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 photoThe line
Operator partThe six buttons, then the two touch targets, each with its own screenThe ring
Rows afteraudio, haptic, mag, pmic, gauge, ioexp, clocks, ir, nfc, rfid, mem, beside the operatorThe ring
Endtiming=all, then poll state=doneThe 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.

KindShownBy
Screen connectingThe connectors going intest_dot_screen phase=connecting
Screen autoHands off, with the grid of the rows. A new grid over an old one repaints only the cells that changedThe start of a run, and each row
Screen ledA black glass, while the camera looks at the LEDThe camera part
Screens buttons, touchThe key or the target asked for, blinking, and the ones doneThe operator part
Screen resultPASS or FAIL over the whole glasstest_dot_result, and a run that aborts
Patternsmarks, white, grey 128, red, green, blue, and off to take a pattern awayThe camera part
EndNo screen: the host leaves the glasstest_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.

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.

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

ThingValue
KconfigKODE_TEST_DOT, in kaos/Kconfig. Default n in the SDK; y in kodeOS's sdkconfig.defaults
NeedsKODEOS_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 defaultsCONFIG_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 addsesp_cam_sensor ==2.6.0 and esp_sccb_intf ==0.0.9, fetched only with the option on
Offtest 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
Level1, KODE_PRIO_TEST_DOT, the level of KODE_PRIO_USER_LOW. With the option on KODE_PRIO_USER_LOW is not defined
Startkaos_testdot_start(), a row of kode_init() after the console and before AO_Power, in either boot role
Layer ruleesp_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

ThingValue
Queue32 events (KODE_TEST_DOT_QUEUE_LEN)
Stack4096 bytes (KODE_TEST_DOT_STACK, the medium tier), static, internal RAM. The AO writes the lock to NVS, so it is not in PSRAM
WorkerOne task at level 1 with a 16 KB stack in PSRAM (KODE_TEST_DOT_WORKER_STACK), holding the running job and 2 more
RecordsAt 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 planes4 planes of 1,672,800 bytes, PSRAM, taken by the first verb that needs the camera
PhotosFive 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 framesThree buffers of at least 1056 x 768 x 2 bytes while the camera is open
Poll ring64 records of 192 bytes, taken at the start of a run
Total in PSRAMAbout 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.

VerbKeysWhat it does and answers
test component=allnoneThe run. Rows, camera and LED on the open line, >ok at the camera's end. Wakes
test_dot_placewin="x,y", cam_ms, mark_ms, dx_mm, degLooks for the four marks until the screen sits inside the limits, place records, the location blob, >ok. Wakes
test_dot_pollnoneThe ring, poll dropped= if any went, then poll state= lane=
test_dot_screenphase=connecting|result, verdict=pass|failPuts a screen of the station's own on the glass, screen phase= ok=
test_dot_photosonly=1..255, a photo's number from 1The kept photos as blobs named photo_01_... upward, photos total= first (not with only) and photos n= bytes= ms= last
test_dot_blobname="location"|"colours"One blob of the last pass again. no_blob when it was not kept
test_dot_relaysstateWrites the relay byte, relays state= ok=. Wakes
test_dot_adcch=0..3One conversion, adc ch= mv=. Wakes
test_dot_loopcount=1..100, default 20Frames round the dock's pads at 9600 and 115200 baud and back, loop
test_dot_radiossid24, ch24, ssid5, ch5, ble, discoverThe C5 card, two Wi-Fi networks and a BLE name: radio_card, radio_time and radio
test_dot_booststate=on|offThe pads delivery test's boost, boost on= fault= current_ma=
test_dot_resultverdict=pass|failThe verdict. A pass runs the factory reset
test_dot_lockconfirm=onStores the lock
test_dot_endnoneGives everything back
test_dot_infononetest_dot state= locked= run= psram_free= j3=
test_dot_cameraonly=framing|pixels|lines|zones|colour|ledThe camera part alone, or one of its parts. Wakes

The timeouts

Every wait of the AO has a bound, held in kqp_config.h.

WaitBound
J3's supply45 s (KODE_TEST_DOT_RAIL_MS)
The camera's probe2 s (KODE_TEST_DOT_PROBE_MS)
A worker job20 s (KODE_TEST_DOT_JOB_MS)
A JPEG from AO_Display2 s (KODE_TEST_DOT_JPEG_MS)
A screen from its host2 s (KODE_TEST_DOT_SCREEN_MS)
The boost's answer, the dock's pins5 s (KODE_TEST_DOT_ANSWER_MS)
The radio check33 s (KODE_TEST_DOT_RADIO_MS): KODE_RADIO_PROBE_MS (8 s) plus 25 s behind a running join
A discovery sweep148 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 loop30 s (KODE_TEST_DOT_LOOP_MS): the dock's pins lent, the frames' job, the pins back
The end50 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 PASS290.15 s (KODE_TEST_DOT_RESET_MS is KODE_RESET_CONSOLE_MS, the sum of the reset's steps)
The console's line295.15 s (KODE_TEST_DOT_LINE_MS): the reset plus a margin of 5 s
A whole run240 s (KODE_TEST_DOT_RUN_MS)
A lent blob coming back60 s (KODE_TEST_DOT_BLOB_MS)
The operator's screen before the first prompt150 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

NameDirectionWhat it is
TEST_DOT_REQ_SIGin, postedA verb from the console (TestDotReqEvt)
TEST_DOT_REC_SIG, TEST_DOT_ANS_SIG, TEST_DOT_LEND_SIGout, posted to the askerA 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_SIGin, postedThe console has written the lent blob and reads it no more
TEST_DOT_SCREEN_REQ_SIGout, publishedA screen or a pattern for the LVGL owner (TestDotScreenEvt: kind, seq, up to 18 bytes of arguments)
TEST_DOT_SCREEN_DONE_SIGin, postedThe host shows it (TestDotScreenDoneEvt: kind, seq)
test_dot_screen_done(kind, seq)callWhat the host calls once the frame is on the panel
test_dot_run_request()callThe console hands test component=all over
test_dot_busy()callTrue from the start of a run to its end. The console refuses factory_reset as busy while it is
test_dot_blob_back()callThe 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.

  1. 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=free and >ok.
  2. 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.
  3. Run test_dot_info again. The state is still dormant, j3 is free and psram_free is the figure of step 1.

In the SDK