Taking a sensor app apart: the four-beat skeleton of every program
Module 1 — Getting started: run the real thing, then take it apart · Slides: slides.md · Module overview · Course page
Take three working sensor apps apart until the shared four-beat skeleton shows — import → create widgets once → a read-compute-draw loop → ui.poll with a back button — and meet sensors.bmi270.motion, sensors.radar and dsp.tilt.
Objectives
Section titled “Objectives”By the end of this lesson, you will:
- Place each line of 01_imu_6axis.py, 02_imu_tilt_fusion.py and 04_radar_presence.py in one of the four beats, and state how the three apps differ in the read, compute and draw slots.
- Explain why widgets are created before the loop and the screen is redrawn only when a value changes, naming at least two harms of doing the opposite.
- Read sensors.bmi270.motion() and sensors.radar(), state the accel (m/s²) and gyro (dps) units, compute the acceleration magnitude |a|, and turn accel into roll and pitch with dsp.tilt.
Before you start
Section titled “Before you start”You’ve been through lessons 1.1–1.3. This lesson builds on the program skeleton you glimpsed in lesson 1.3. Keep 01_imu_6axis.py and 04_radar_presence.py open in BENTO IDE.
- Hardware: a TESAIoT Dev Kit board already flashed with BENTO’s MicroPython firmware, or the BENTO Emulator inside BENTO IDE — real radar exists on the TESAIoT Dev Kit; on the emulator, the radar’s presence value is simulated with the Shake button.
- Prior lesson: lesson 1.3 — Hands-on: your first model menu
See it work first
Section titled “See it work first”Always run the real thing before taking it apart: run 01_imu_6axis.py and move or shake the board — the six-axis numbers and the |a| bar follow along. Then run 04_radar_presence.py, and compare sitting still in front of the board with stepping out of range: the label switches from CLEAR to PRESENCE.
Concepts
Section titled “Concepts”The three apps — 01 (six-axis numbers), 02 (a tilt-angle gauge), and 04 (a present-or-not label) — use different sensors and different screens, but all follow the same four beats: (1) import the modules and call ui.screen() right away; (2) create every widget before the loop, keeping a variable for any one whose value will change; (3) a loop that reads → computes → draws, then pauses with time.sleep_ms(100); (4) a non-blocking ui.poll() that, on finding the back button’s handle, does raise KeyboardInterrupt to exit and clean up in an except. Beats 1–2 run once; beats 3–4 repeat until you exit. Once you can see this skeleton, reading new code gets much faster, because you know exactly where to look for each thing.
The rule most often broken is: what “is” gets created before the loop; what “changes” happens inside the loop. If a widget is created inside the loop, the screen flickers, and widgets pile up until memory runs out. Another habit, from 04, is to only redraw when a value changes, by keeping the last-drawn value to compare against (last, was, last_seq), which keeps the screen steady and doesn’t unnecessarily saturate the cross-core communication channel. A deadzone helps further, so tiny jitters in the angle near zero don’t make the needle twitch.
sensors.bmi270.motion() returns (ax, ay, az, gx, gy, gz) in one call, so every axis comes from the same instant. Accel is in m/s² (lying flat, az ≈ 9.8); gyro is in degrees per second. The vector magnitude $|a| = \sqrt{a_x^2 + a_y^2 + a_z^2}$ collapses three axes into one number — around 9.8 at rest, spiking when shaken. sensors.radar() returns a dict with presence and energy. And dsp.tilt(ax, ay, az) computes (roll, pitch) in degrees for you, in C, because heavy maths should be handed off to C, leaving MicroPython to handle logic and the screen.
Worked example
Section titled “Worked example”Follow PRIMM: Predict what each app shows before running it → Run it → Investigate with the three-app comparison table in the slides. 02_imu_tilt_fusion.py is the tilt-angle gauge app that uses dsp.tilt, and is the template for the Tilt Monitor in lesson 1.5.
| File | What this file teaches |
|---|---|
| examples/01_imu_6axis.py | IMU BMI270: a six-axis dashboard on screen |
| examples/04_radar_presence.py | Radar: a large status label that changes colour when someone is detected (event-driven) |
| examples/02_imu_tilt_fusion.py | Tilt angle: a pair of arc gauges (pitch / roll) plus Seg7 digits |
This lesson’s slides also reference a file in another lesson:
- m01-onboarding/l05-sensor-remix-lab/practice/s02_anatomy_sensor.py — taking apart a sensor app’s structure, then remixing it yourself (the fill-in-the-code version)
Check your understanding
Section titled “Check your understanding”The same questions are in quiz.yaml for automated checking.
-
Which part of apps 01, 02 and 04 is identical, line for line, enough to copy across apps directly? (single choice · objective 1)
- a) The sensor-reading slot
- b) The computation slot
- c) The ui.poll loop with the back button that raises KeyboardInterrupt
- d) The kind of widget created
Solution
c — the three-app comparison table shows they only differ in “what’s read, what’s computed, what it’s drawn with”. The poll loop and the back button are exactly identical.
-
If you accidentally create a new ui.Seg7 every round inside the loop, what happens? (select every correct answer) (multiple choice · objective 2)
- a) The screen flickers, because it’s redrawn on top of itself every frame
- b) Old widgets pile up until memory runs out and it hangs
- c) The loop gets faster, since there’s no variable to keep
- d) The sensor’s values become more accurate
Solution
a, b — create a widget once before the loop, then just change its value inside the loop, such as seg.text(“42”), which keeps the screen steady and memory usage constant.
-
In 04_radar_presence.py, what does the line
if now != wasdo? (single choice · objective 2)- a) Stops the loop when no one is present
- b) Only redraws the screen when the status changes; leaves it untouched when the value is the same
- c) Restarts the radar every round
- d) Waits until someone walks in
Solution
b — this is event-driven drawing: it keeps the latest status in
was, and only redraws when the new value differs from it. -
With the board lying flat and still on a table, which values from sensors.bmi270.motion() make the most sense? (single choice · objective 3)
- a) ax ≈ 9.8, ay ≈ 0, az ≈ 0
- b) ax ≈ 0, ay ≈ 0, az ≈ 9.8, and all three gyro axes near 0
- c) Every axis is 0, because the board isn’t moving
- d) az ≈ 1.0, because the unit is g
Solution
b — accel is in m/s² and also measures gravity, so the axis perpendicular to the ground reads around 9.8. Gyro measures rotation, which is near 0 when still.
-
If ax = 0, ay = 6, az = 8 m/s², what is |a|? (single choice · objective 3)
- a) 14
- b) 10
- c) 7
- d) 100
Solution
b — |a| = √(0² + 6² + 8²) = √100 = 10 m/s², a 3D Pythagorean calculation. A value close to 9.8 means the board is barely accelerating — it’s mostly gravity split across two axes.
- Run
01and04(if you have a board), and note what changes on screen as you move or approach it. - Write a four-beat table for all three apps in your learning log — which slots are identical line for line, and which differ.
- Lay the board flat and read |a| until it’s close to 9.8, then tilt it and observe how gravity spreads across ax and ay.
Going further
Section titled “Going further”In lesson 1.5, we’ll remix these three apps into our own Tilt Monitor by filling in four blanks in a practice file.
Next lesson: lesson 1.5 — Hands-on: our own Tilt Monitor remix
Reflect
Section titled “Reflect”- If you had to change app
01to read radar instead of the IMU, which beats would you need to change, and which wouldn’t need touching at all? - Why does reading all six axes in one call matter for computing the angle?
Review questions
Answer on your own first, then open the answer.
-
Which part of apps 01, 02 and 04 is identical line for line, so it can be copied across apps? (Objective 1)
- ช่องอ่านเซนเซอร์
- ช่องคำนวณ
- ลูป ui.poll กับปุ่ม back ที่ raise KeyboardInterrupt
- ชนิดของ widget ที่สร้าง
Show answer
Answer: C. ลูป ui.poll กับปุ่ม back ที่ raise KeyboardInterrupt
ตารางเทียบสามแอปแสดงว่าต่างกันแค่ "อ่านอะไร คำนวณอะไร วาดด้วยอะไร" ส่วน poll กับปุ่ม back เหมือนกันเป๊ะ
-
If you accidentally create a new ui.Seg7 on every loop pass, what happens? (select all that apply) (Objective 2)
- จอกระพริบเพราะวาดทับซ้ำทุกเฟรม
- widget เก่ากองสะสมจนหน่วยความจำหมดแล้วค้าง
- ลูปเร็วขึ้นเพราะไม่ต้องเก็บตัวแปร
- ค่าจากเซนเซอร์แม่นขึ้น
Show answer
Answer: A. จอกระพริบเพราะวาดทับซ้ำทุกเฟรม · B. widget เก่ากองสะสมจนหน่วยความจำหมดแล้วค้าง
สร้าง widget ครั้งเดียวก่อนลูป แล้วในลูปแค่แก้ค่า เช่น seg.text("42") จอจึงนิ่งและหน่วยความจำคงที่
-
In 04_radar_presence.py, what does the line if now != was do? (Objective 2)
- หยุดลูปเมื่อไม่มีคน
- วาดจอเฉพาะตอนสถานะเปลี่ยน ไม่แตะจอเมื่อค่าเดิม
- เปิดเรดาร์ใหม่ทุกรอบ
- รอจนกว่าจะมีคนเข้ามา
Show answer
Answer: B. วาดจอเฉพาะตอนสถานะเปลี่ยน ไม่แตะจอเมื่อค่าเดิม
นี่คือการวาดแบบ event-driven เก็บสถานะล่าสุดไว้ใน was แล้ววาดใหม่เฉพาะตอนที่ต่างไปจากเดิม
-
With the board lying flat and still, which reading from sensors.bmi270.motion() is most plausible? (Objective 3)
- ax ≈ 9.8, ay ≈ 0, az ≈ 0
- ax ≈ 0, ay ≈ 0, az ≈ 9.8 และ gyro ทั้งสามแกนใกล้ 0
- ทุกแกนเป็น 0 เพราะบอร์ดไม่ขยับ
- az ≈ 1.0 เพราะหน่วยเป็น g
Show answer
Answer: B. ax ≈ 0, ay ≈ 0, az ≈ 9.8 และ gyro ทั้งสามแกนใกล้ 0
accel มีหน่วย m/s² และวัดแรงโน้มถ่วงด้วย แกนที่ตั้งฉากพื้นจึงได้ราว 9.8 ส่วน gyro วัดการหมุน วางนิ่งจึงใกล้ 0
-
If ax = 0, ay = 6 and az = 8 m/s², what is |a|? (Objective 3)
- 14
- 10
- 7
- 100
Show answer
Answer: B. 10
|a| = √(0² + 6² + 8²) = √100 = 10 m/s² เป็นพีทาโกรัสในสามมิติ ค่าใกล้ 9.8 แปลว่าบอร์ดแทบไม่ได้เร่ง มีแค่แรงโน้มถ่วงที่ถูกเอียงไปสองแกน
Cite this lesson
If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.
"Taking a sensor app apart: the four-beat skeleton of every program" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0
Thai attribution: "แกะแอปเซนเซอร์: โครงร่วมสี่จังหวะของทุกโปรแกรม" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA