Sensors and Events
While a Kamoji Mini App is on screen, Kamoji writes its live sensor values into global
tables 10 times a second. Read them in bindings ("{{ imu.lat_g }}") or
in Lua.
A value is nil until its first reading arrives, which takes up to a tenth of
a second after the mini app opens, so guard it: imu.lat_g or 0.
imu: motion
Declare sensors.imu. Axes as everywhere in the Kamoji firmware:
- x: sideways, positive to the right
- y: along the car, positive when accelerating
- z: vertical
The accelerations are measured from Kamoji's resting position, which it learns when it calibrates at power-up, so all three read about 0 while the car is still or cruising steadily.
| Value | Unit | Meaning |
|---|---|---|
imu.accel_x, imu.accel_y, imu.accel_z | m/s² | Acceleration on each axis |
imu.gforce_x, imu.gforce_y, imu.gforce_z | g | The same in g |
imu.lat_g | g | Sideways g: the same as gforce_x |
imu.long_g | g | Forward g: the same as gforce_y (positive accelerating, negative braking) |
imu.gyro_x, imu.gyro_y, imu.gyro_z | °/s | Rotation speed around each axis |
drive: the Excite-O-Meter's scores
Declare sensors.imu. The values behind the Excite-O-Meter, computed by
Kamoji from the motion sensor:
| Value | Range | Meaning |
|---|---|---|
drive.excitement | 0–100 | Excitement score: how lively the driving has been, averaged over the last 30 seconds |
drive.comfort | 0–100 | Comfort score: how smooth it has been, averaged over the last 30 seconds |
drive.excitement_now | 0–100 | Excitement right now |
drive.comfort_now | 0–100 | Comfort right now |
drive.hard_turn | true / false | true while the car is cornering hard |
drive.hard_brake | true / false | true while it is braking hard |
drive.hard_accel | true / false | true while it is accelerating hard |
drive.hard | true / false | true while any of the three is |
The hard_* values are states, true for as long as the move lasts; the
events below fire once per move.
light
Declare sensors.light.
| Value | Unit | Meaning |
|---|---|---|
light.raw | V | The ambient light sensor's reading, smoothed. It is not calibrated to lux: compare it with earlier readings rather than with a fixed number |
env
Declare sensors.env.
| Value | Unit | Meaning |
|---|---|---|
env.temp_c | °C | The motion sensor chip's temperature: Kamoji's own warmth, not the cabin's |
time
No permission needed.
| Value | Meaning |
|---|---|
time.valid | true once Kamoji knows the time, else false |
time.epoch | Seconds since 1 January 1970, UTC |
time.hour, time.min, time.sec | Time of day in the firmware's own time zone (see below) |
time.day, time.month, time.year | Date, same time zone |
time.weekday | 1 = Sunday … 7 = Saturday |
Kamoji learns the time from the internet when it is on Wi-Fi, or from your
phone when the Kamoji app connects; either one makes time.valid true.
Until then, time.valid is false and the other values are nil.
time.hour and its neighbours use the time zone built into the firmware,
which is not necessarily the user's. For a clock, give your mini app a
utc_offset setting and use clock() instead:
local t = clock(param.utc_offset) -- nil until the time is known
if t then print(fmt("%02d:%02d", t.hour, t.min)) end
clock(offset) returns { year, month, day, hour, min, sec, weekday } for
time.epoch shifted by offset hours (fractions like 5.5 or 5.75 work).
It does not apply daylight saving time; users change the offset in the mini app's
settings.
device
| Value | Unit | Meaning |
|---|---|---|
device.uptime_s | s | Time since Kamoji started |
nav: navigation from the phone
Declare phone.navigation. Turn-by-turn navigation from the phone: on Android the
Kamoji app forwards Google Maps' navigation (the user turns it on, and grants
notification access, after installing a mini app with this permission). Kamoji also
accepts it from the Chronos app.
| Key | Meaning |
|---|---|
nav.active | true while navigation is on |
nav.routing | true while a route is being followed |
nav.title | Distance to the next turn, e.g. "250 m" |
nav.directions | The instruction, e.g. "Turn left onto Orchard Road" |
nav.distance | Distance left to the destination |
nav.duration | Time left |
nav.eta | Arrival time |
nav.has_icon | true when there is a turn arrow |
nav.linked | true while a phone is connected to Kamoji over Bluetooth. Show "Waiting for your phone" when it is false (a restarted Kamoji is reconnected by the app within seconds) |
nav.traffic | Traffic on the rest of the route: "clear", "slow", "heavy", or "" when unknown (Android 16 and later, Google Maps) |
nav.strip | The road ahead (next 20 km) in 24 cells: c clear, s slow, h heavy, - beyond the end of the route |
nav.has_text_image | true when the phone drew the instruction itself (scripts Kamoji's fonts lack): show builtin:nav_text instead of nav.directions |
The arrow is an image: "src": "builtin:nav_icon" (48 x 48) or
"builtin:nav_icon_large" (96 x 96), white on transparent. All texts are as the
phone sends them, already in the user's language and units.
Things to know when you lay out a navigation mini app:
- Google Maps often puts the distance and the instruction in one title,
such as
"8.6 km · At the roundabout, take the 2nd exit", and leavesnav.directionsempty. Split the title on·to show the distance big and the instruction under it (the Navigation example does this). - Kamoji's fonts cover Latin (including Vietnamese) and Thai in the small
and medium sizes; the large and huge sizes are Latin only, so use them for
distances and times. For other scripts (Khmer, Burmese, Chinese, Arabic and
so on) the phone sends the instruction as a 224 x 56 image:
"src": "builtin:nav_text", shown whennav.has_text_imageistrue. - Colour the arrival time by
nav.trafficthe way Google Maps does: green forclear, orange forslow, red forheavy.
obd: car data (OBD-II)
Declare sensors.obd. Live readings from the car's OBD-II port, through an
ELM327 Bluetooth adapter the user paired in Kamoji's settings (firmware 0.2.2
and later). While a mini app with this permission is on screen, Kamoji keeps
asking the car for them: RPM about 10 times a second, speed once a second,
fuel level and voltages every 10 to 30 seconds.
| Key | Unit | Meaning |
|---|---|---|
obd.available | true when this firmware offers OBD-II adapter pairing | |
obd.connected | true while the paired adapter is connected and ready | |
obd.rpm | rpm | Engine speed |
obd.speed | km/h | Vehicle speed |
obd.fuel | % | Fuel level |
obd.voltage | V | Battery (control module) voltage |
obd.battery | % | EV high-voltage battery level (cars that report it) |
obd.has_rpm, obd.has_speed, obd.has_fuel, obd.has_voltage, obd.has_battery | true once the car answered that reading on this connection | |
obd.tries_rpm, obd.tries_speed, obd.tries_fuel, obd.tries_voltage, obd.tries_battery | How often it was asked on this connection |
A reading the car never answered stays 0, so check its has_ flag before
showing it. Electric cars answer no RPM and no fuel level: when
obd.tries_rpm reaches a few (say 4) while obd.has_rpm and obd.has_fuel
are still false, treat the car as an EV and show speed and obd.battery
instead. If nothing at all answers after a few tries, the car doesn't support
these readings (or the ignition is off).
function phase()
if not obd.available then return "unavailable" end
if not obd.connected then return "disconnected" end
local asked = (obd.tries_rpm or 0) >= 4
if asked and not obd.has_rpm and not obd.has_fuel then
return obd.has_speed and "ev" or "unsupported"
end
return obd.has_rpm and "ok" or "reading"
end
Events
Declare events.drive. These are the same events that drive Kamoji's own
reactions and its Black Box. Each one calls a handler in main.lua with its
magnitude, or runs the matching entry of the layout's on (without the
magnitude):
| Event | Handler | Magnitude |
|---|---|---|
bump | on_bump(m) | Size of the vertical jolt, m/s² (positive). One call per jolt |
hard_brake | on_hard_brake(m) | Braking deceleration, m/s² (positive) |
hard_accel | on_hard_accel(m) | Acceleration, m/s² (positive) |
lean | on_lean(m) | Sideways acceleration of hard cornering, m/s²: positive to the right, negative to the left |
Divide by 9.81 for g. hard_brake, hard_accel and lean fire once when
the value crosses Kamoji's threshold, and again only after it has settled
back. Events start once Kamoji has finished starting up; hard_brake,
hard_accel and lean also wait until it has calibrated.
Like everything else, events only reach the mini app on screen.
state.bumps = 0
function on_bump(m)
state.bumps = state.bumps + 1
state.last = fmt("%.1f g", m / 9.81)
end
Gestures
Declare touch. on_top_tap(), on_top_hold(), on_pinch(), and on_left_tap() / on_right_tap() for mini apps that take the side taps: see
Handlers.
Coming later
The microphone (mic), tilt angles, light in lux, and the events
bumpy_road, cornering, idle_start / idle_end, Wi-Fi and phone
connection events are designed but not available yet. The gyro and mic
tables exist but stay empty.