The calls every app touches: putting text on screen, reading the d-pad, writing to the log, and shutting down. All ungated — no capability, no prompt — except request_cap, which exists precisely to trigger one.

App control

set_frame

Sets the text content displayed on the OLED above the canvas area.

Capability: None

jpp_sdk_status_t jpp_sdk_set_frame(jpp_sdk_context_t *ctx,
                                    const char *const *lines,
                                    size_t line_count);
jppsdk.set_frame(lines: list[str]) -> None

Parameters:

Name Description
lines Array of text rows. Each row fits ~21 characters; longer strings are silently truncated.
line_count Number of rows. Maximum 7. Extra rows beyond the limit are dropped.

Returns: JPP_SDK_OK, or JPP_SDK_TEXT_TRUNCATED if any line was clipped.

Notes: - Calling set_frame clears fullscreen canvas mode. If you were using canvas_fullscreen(true), re-enable it after each set_frame call. - Row 0 acts as the title — if non-empty, a 1-px signature rule is drawn below it. - The frame persists until the next set_frame. You do not need to call it on every idle tick; call it only when content changes.


request_close

Signals the firmware to close the app and return to the launcher.

Capability: None

jpp_sdk_status_t jpp_sdk_request_close(jpp_sdk_context_t *ctx);
jppsdk.request_close() -> None

Notes: - For MicroPython apps, the firmware calls on_stop and then tears down the runtime. You can still do cleanup after request_close() returns — the app is not immediately killed. - For native apps, call request_close then return from jpp_app_entry. The firmware closes the app when the function returns; you can also return without calling it and the firmware will close cleanly. - Calling request_close inside on_idle sets a flag; the app runs until on_stop completes.


log

Emits a named event to the device log (visible over serial monitor).

Capability: None

jpp_sdk_status_t jpp_sdk_log(jpp_sdk_context_t *ctx,
                              const char *event_name);
jppsdk.log(event_name: str) -> None

Notes: Useful for debugging. Log lines appear on the device console (native USB-Serial-JTAG) with the tag app_log.


request_cap (C only)

Proactively triggers the consent prompt for a single manifest-declared capability, without performing any operation. Use it to front-load permission requests — ask for the caps a screen or mode needs the moment the user chooses it, rather than letting the prompt fire mid-flow at first use.

Capability: the one named by cap (must be declared in the manifest)

jpp_sdk_status_t jpp_sdk_request_cap(jpp_sdk_context_t *ctx,
                                     const char *cap);

Returns: JPP_SDK_OK if the cap is already granted or the user allows it; JPP_SDK_ACCESS_DENIED if the user declines or the cap was not declared in the manifest; JPP_SDK_INVALID_ARGUMENT if cap is NULL/empty.

Notes: - This only changes when the prompt appears, never the policy. Tier-1 caps (e.g. ble.scan, ble.advertise, http.request, background.register) persist once granted; tier-2 caps (e.g. files.full, ble.connect, ble.host, network.bind) are granted for the session only and re-prompt on the next launch — identical to first-use consent. - Requesting an already-granted cap is a cheap no-op that returns JPP_SDK_OK without prompting, so it is safe to call on every entry to a screen. - https.request is a partial exception: requesting it front-loads the capability prompt, but the per-origin prompt still fires on the first request to each new host, because the origin is not known until the call. - During a headless background run every request is denied (JPP_SDK_ACCESS_DENIED), matching the first-use rule. - MeetApp is the reference user: it requests ble.scan/ble.advertise at startup and the mode-specific ble.connect/ble.host the moment the user picks "Initiate" or "Join".


Key input

poll_key

Returns the next key event from the queue without blocking. Returns KEY_NONE immediately if no key is pending.

Capability: None

jpp_sdk_status_t jpp_sdk_poll_key(jpp_sdk_context_t *ctx,
                                   jpp_sdk_key_event_t *out_event);
jppsdk.poll_key() -> int

Returns: - C: JPP_SDK_OK; *out_event is the key constant (may be JPP_SDK_KEY_NONE) - Python: the key constant as an int

Notes: Use this in on_idle where you want to check for keys but not block the idle loop.


wait_key

Blocks until a key event is available or the timeout elapses.

Capability: None

jpp_sdk_status_t jpp_sdk_wait_key(jpp_sdk_context_t *ctx,
                                   uint32_t timeout_ms,
                                   jpp_sdk_key_event_t *out_event);
jppsdk.wait_key(timeout_ms: int) -> int

Parameters:

Name Description
timeout_ms Maximum wait time in milliseconds. Pass 0 to wait indefinitely.

Returns: The key constant. JPP_SDK_KEY_NONE if the timeout elapsed with no key.

Notes: Use this as the main loop driver in native apps or in MicroPython blocking sequences. Pass a short timeout (e.g. 100 ms) when you need periodic work between key events.


push_key (C only)

Injects a synthetic key event into the queue. Useful for testing and for UI helpers that need to replay a key.

Capability: None

void jpp_sdk_push_key(jpp_sdk_context_t *ctx, jpp_sdk_key_event_t event);

claim_center

Take over CENTER gestures as your own input.

Capability: None

Requires SDK level 2.

claim_center, KEY_BACK, KEY_CENTER_HOLD, KEY_CENTER_DOUBLE and the CENTER_CLAIM_* constants all arrived in firmware v1.1. Declare "sdk_min": 2 — see the SDK changelog.

jpp_sdk_status_t jpp_sdk_claim_center(jpp_sdk_context_t *ctx, uint8_t mask);
jppsdk.claim_center(mask: int) -> None

Parameters:

Name Description
mask Bitwise OR of JPP_SDK_CENTER_CLAIM_HOLD and JPP_SDK_CENTER_CLAIM_DOUBLE, or JPP_SDK_CENTER_CLAIM_NONE (the default on every bind). In MicroPython: jppsdk.CENTER_CLAIM_HOLD, jppsdk.CENTER_CLAIM_DOUBLE, jppsdk.CENTER_CLAIM_NONE.

Behaviour:

Claim Your app receives Back
(nothing — the default) KEY_CENTER KEY_BACK
HOLD KEY_CENTER + KEY_CENTER_HOLD your own
DOUBLE KEY_CENTER + KEY_CENTER_DOUBLE your own
HOLD \| DOUBLE KEY_CENTER + both your own

Notes: The device has a user preference (Settings > Controls) for whether a long hold or a double-click means "Back". Your app never needs to read it. Claim nothing and you get JPP_SDK_KEY_BACK whenever the user asks to go back, with the firmware deciding which physical gesture that was — settings-agnostic by construction.

Claim a gesture and it becomes yours: it arrives as JPP_SDK_KEY_CENTER_HOLD / JPP_SDK_KEY_CENTER_DOUBLE, JPP_SDK_KEY_BACK stops being delivered, and your app is responsible for its own way out (a pause menu, an on-screen Exit item). That is the trade for owning the gesture, and it applies whichever gesture you claimed.

Claiming only HOLD additionally keeps JPP_SDK_KEY_CENTER instant. Telling a double-click apart requires withholding the first click for a few hundred milliseconds; when nobody needs that distinction, nothing is withheld. This is the combination for an app where CENTER is a rapid action button and hold opens a pause menu:

jpp_sdk_claim_center(ctx, JPP_SDK_CENTER_CLAIM_HOLD);
/* ... */
switch (key) {
case JPP_SDK_KEY_CENTER:      fire();       break;
case JPP_SDK_KEY_CENTER_HOLD: pause_menu(); break;
}

Never affects UP/DOWN/LEFT/RIGHT, and never affects the launcher or Settings. The claim lives on your context and is dropped when your app exits.

JPP_SDK_KEY_BACK is the preferred spelling of JPP_SDK_KEY_CENTER_LONG — the same value under a name that no longer implies a particular gesture. Existing code using JPP_SDK_KEY_CENTER_LONG is unaffected.