SPIKE examples: SharpShot
Back to SharpShot | How SPIKE reads it | Value reference
Note
SPIKE hardware-verified (2026-11-01, release v1.0-261101): the official App (Education and Robot Inventor), the Pybricks library, Pybricks built-in blocks and the examples on this page all ran on real hubs.
This page has 6 Pybricks examples (with the MBC library MBC_UniCam.py) and 2 LEGO SPIKE App (Education) Python examples, plus a table for the official App blocks (Education and Robot Inventor). The tasks are the ones students use most: turn to follow a blob, find several colours, read the colour at a spot, colour order (WRO), PD line following with a stop at a junction, object detection (find the biggest red triangle).
| Way | For whom | How |
|---|---|---|
| 🐍 Pybricks (Python) | You want every value (14 colours, counts, 14 line values) | Open code.pybricks.com → "Import a file" → import the example and MBC_UniCam.py → open the example → ▶ |
| 🟨 LEGO SPIKE App 3 (Education) Python | School SPIKE App, no firmware change | New "Python" project → paste the whole example → ▶ (the reading helper is already at the top) |
| 🟨 Official App blocks (Education / Robot Inventor) | Blocks only | Use the colour, reflection and raw red/green/blue blocks with the table at the bottom |
Important
- The Pybricks library switches the camera mode for you:
UniCam(Port.C, mode=13)sets the camera to 13 when it is created (mode number = camera mode × 10 + option; colour order = 30), no need to use the menu first (shipping firmware; with firmware from before 2026-10-29 set the same mode in the camera menu yourself). The official App cannot switch it remotely; set it in the camera menu.- Pybricks needs the Pybricks firmware on the hub (install it from code.pybricks.com; the SPIKE App can put LEGO firmware back). The official App needs nothing.
- The camera must run the SPIKE firmware. It detects by itself whether the hub runs the official App or Pybricks.
All host libraries in one download: MBC_UniCam_v1_Host_Libraries.zip (Pybricks, EV3, SPIKE App, Arduino, micro:bit, MATRIX)
Before you start: teach colours and set the mode on the camera
- Teach a colour: on the live view use left/right to stop on "target 1" → OK → move the cross onto the colour (AxisX → AxisY) → OK (hold OK for 0.8 s to finish at once). Do the same for target 2, 3… The side key on a target clears it.
- Set the mode: left/right to MODE → OK → camera mode 1~5 → OK → option → OK. Options show how many values this hub really gets (for example Pybricks "7 XY"). "1-3" at the top right of the screen is the current mode (= mode number 13). The option counts are decided by the hub when you enter the menu: go back to the live picture, let the hub start reading, then enter the menu; staying inside the menu does not update the counts.
- Object detection (camera mode 5): after teaching targets 1
8 the camera asks8 become objects.Target N shape; pick a shape (Any = not set) or skip with the side key. Only target colours 1 - Settings survive power-off (colours, mode, options, target shapes).
| Mode number | Camera menu | What Pybricks gets |
|---|---|---|
| 11 | 1 Colour tracking, 1 Max | 14 colour positions (16×12 grid, library turns it into pixels ±5); counts via mode 6 |
| 12 | 1 Colour tracking, 2 Multi | 7 colours X, Y (pixels); counts via mode 6 |
| 13 | 1 Colour tracking, 3 Standard | 4 colours X, Y, area %, count |
| 14 | 1 Colour tracking, 4 Fine | 2 colours X, Y, width, height, count, area %; colours found |
| 21 / 22 / 23 | 2 Spot colour, 1/2/3 | 14 spots hue / 7 spots hue + sat / 4 spots hue + sat + val |
| 30 | 3 Colour order | hue codes of 13 blocks from the left + total |
| 41 / 42 / 43 | 4 Line: black / white / colour | 14 values (near/mid/far offset, angle, width, flags, junction, edges, markers, lost side) |
| 51 | 5 Object detection, 1 Object list | code, X, Y (pixels) of 4 objects + object count |
| 52 | 5 Object detection, 2 Object detail | colour, shape, X, Y, width, height, area % of 2 objects (no count, no angle) |
Reading the numbers: pixels, X 0159 (left → right), Y 0119 (top → bottom), (0, 0) = not found. Hue code 0255: red 0/240, yellow 40, green 80, cyan 120, blue 160, purple 200; 241248 white/grey; 249255 black. Line offsets −100100: standing behind the robot, line on the left = negative.
Objects (modes 51 / 52): object code = colour × 10 + shape (13 = a triangle of target 1), 0 = none. Shapes: 0 other, 1 circle, 2 ellipse, 3 triangle, 4 square, 5 rectangle, 6 pentagon, 7 hexagon, 8 star, 9 trapezoid. Objects 1, 2… are sorted largest first; X, Y is the centroid. Only target colours 1~8 count.
MBC_UniCam.py methods
cam = UniCam(Port.C, mode=13) (port can also be 1~6; UniCam(Port.C, 13, True) = multitask, await every method)
| Method | Returns | Modes |
|---|---|---|
xy(c), x(c), y(c), found(c) |
(X, Y) pixels of colour c, found or not | 11~14 |
area(c) |
area (% of the picture) | 13, 14 |
size(c) |
(width, height) pixels | 14 |
count(c) |
number of blobs of colour c (mode 11: 0~7, mode 6) | 11~14 |
colors_found() |
how many of the 16 colours are found (11, 12 read mode 6) | 11, 12, 14 |
cell(c) |
16×12 grid cell (1 |
11 |
max_color() / max_point() |
colours / spots in this mode | 1x / 2x |
hue(i), sat(i), val(i), hsv(i), color(i) |
hue code, saturation, value, colour name of spot i | 21~23 |
order(), total() |
hue codes from the left (≤13), total blocks | 30 |
line_all() |
all 14 values at once | 41~43 |
near() mid() far() angle() width() flags() junction() junction_dist() edges() far_width() marker() lost() |
single line values | 41~43 |
max_object() |
objects in this mode: 51 → 4, 52 → 2 | 51, 52 |
object_count() |
objects in view, 0~8 | 51 |
object(i), object_found(i) |
code of object i (colour × 10 + shape, 0 = none), found or not | 51, 52 |
object_color(i), object_shape(i) |
colour 1 |
51, 52 |
object_xy(i) |
centroid (X, Y) pixels, (0, 0) = none | 51, 52 |
object_size(i), object_area(i) |
bounding box (width, height) pixels, area % | 52 |
device_id(), is_unicam(), firmware_version() |
mode 8: (0x4C, 3, 100), True, (1, 0) | all |
sensor_ok(), mode_number() |
last read OK, mode number given | all |
color_name(h) (module function) |
hue code → 'red' … 'black' |
— |
shape_name(s, zh=False) (module function) |
shape 0~9 → 'other' 'circle' 'ellipse' 'triangle' 'square' 'rectangle' 'pentagon' 'hexagon' 'star' 'trapezoid'; zh=True gives Chinese |
— |
- A method that the mode does not have (for example
xy()in mode 21) or a colour number out of range raisesValueErrorwith a bilingual message; it never returns a wrong value silently. - Unplugging does not stop the program: you get the last value;
Noneif it was never read. - Counts (modes 11, 12) are in mode 6 and the device ID in mode 8. A mode switch costs about 30 ms, so do not read them every loop.
Example 1: turn to follow a colour blob
What it does: the robot turns toward colour 1 (left → turn left, right → turn right) while driving forward; when the area is big (close) it only turns; when nothing is seen it spins slowly to search.
Camera mode: camera mode 1 Colour tracking, option 3 Standard (XY + area) = mode number 13; teach "target 1" first.
You need: SPIKE hub, driving base (left wheel E, right wheel F), camera in port C, a ball or brick of the target colour.
Download: ex01_track_turn.py + MBC_UniCam.py
Pybricks code
# 神攝手 × Pybricks(SPIKE)範例 1:跟著色塊轉
# SharpShot × Pybricks (SPIKE) example 1: turn to follow a colour blob
# 需要把相機選單切到 相機模式 1 顏色追蹤 ・子選項 ③ 標準(XY+面積)=模式編號 13
# Camera menu: camera mode 1 Colour tracking, option 3 Standard (XY + area) = mode number 13
# 準備:先在相機上教「目標 1」的顏色 / Teach "target 1" on the camera first
# 接線 Wiring:相機 → C 孔 / camera → port C;左輪 E、右輪 F / left wheel E, right wheel F
# 同一個資料夾要有 MBC_UniCam.py / MBC_UniCam.py must be imported too
from pybricks.hubs import PrimeHub
from pybricks.pupdevices import Motor
from pybricks.parameters import Port, Direction, Color
from pybricks.robotics import DriveBase
from pybricks.tools import wait
from MBC_UniCam import UniCam
hub = PrimeHub()
cam = UniCam(Port.C, mode=13) # 函式庫會自動把相機切到這個模式 / the library switches the camera to this mode
left = Motor(Port.E, Direction.COUNTERCLOCKWISE)
right = Motor(Port.F)
robot = DriveBase(left, right, 56, 112) # 輪徑 56 mm、輪距 112 mm / wheel 56 mm, axle 112 mm
KP = 3 # 👉 可以改:偏 1 像素就多轉幾度/秒 / you can change: turn rate per pixel of error
STOP_AREA = 20 # 👉 可以改:面積 ≥ 20 % 就算很近 / area (%) that counts as "close"
while True:
p = cam.xy(1) # 色 1 的 (X, Y) 像素;(0, 0) = 沒看到 / (0, 0) = not seen
if p is None: # 從來沒讀到相機 / camera never answered
robot.stop()
hub.light.on(Color.RED)
elif p[0] == 0: # 沒看到色 1:原地慢慢轉著找 / not seen: spin slowly to search
robot.drive(0, 60)
hub.light.on(Color.ORANGE)
else:
error = p[0] - 80 # 畫面中間 X = 80;色塊在右 → 正 / centre is X = 80; right = positive
if cam.area(1) >= STOP_AREA: # 很近了:只轉不前進 / close: turn only
robot.drive(0, error * KP)
else: # 還遠:邊前進邊轉 / far: drive and turn
robot.drive(80, error * KP)
hub.light.on(Color.GREEN)
wait(20)Step by step
UniCam(Port.C, mode=13): use mode 13; the library switches the camera to mode 13 when it is created.cam.xy(1): (X, Y) of colour 1 in pixels. X = 0 means not seen.error = X − 80: the centre is 80; right of centre is positive →robot.drive(speed, error × KP)positive turn = turn right.cam.area(1): area %, in the same packet as X and Y (mode 9), no extra wait.
What you should see: ball on the left → the robot turns left; on the right → turns right; ball close (area ≥ 20 %) → it turns on the spot; no ball → orange light and it spins to search.
Common problems
- Turns the wrong way? → swap the E and F cables, or move
Direction.COUNTERCLOCKWISEto the right motor. - Always searching? → the camera screen should show
1-3; target 1 must be taught; with the official App, set mode 13 in the camera menu. - Wobbles left and right? → make
KPsmaller (for example 2).
Example 2: where are my colours?
What it does: every 0.5 s it prints where colours 1~7 are (left/middle/right, top/middle/bottom); the hub screen shows how many are seen. Press the hub's left button to also print "colours found out of 16" and "blobs of colour 1".
Camera mode: camera mode 1 Colour tracking, option 2 Multi = mode number 12; teach targets 1~7 (any number).
You need: SPIKE hub, camera in port C, bricks of a few colours.
Download: ex02_multi_color.py + MBC_UniCam.py
Pybricks code
# 神攝手 × Pybricks(SPIKE)範例 2:多色找位置
# SharpShot × Pybricks (SPIKE) example 2: where are my colours?
# 需要把相機選單切到 相機模式 1 顏色追蹤 ・子選項 ② 多色 =模式編號 12(SPIKE 給 7 色的 X、Y)
# Camera menu: camera mode 1 Colour tracking, option 2 Multi = mode number 12 (7 colours, X and Y)
# 準備:在相機上教目標 1~7 的顏色(教幾色都可以)/ teach targets 1~7 (any number)
# 接線 Wiring:相機 → C 孔 / camera → port C
from pybricks.hubs import PrimeHub
from pybricks.parameters import Port, Button
from pybricks.tools import wait
from MBC_UniCam import UniCam
hub = PrimeHub()
cam = UniCam(Port.C, mode=12)
def where(x, y):
# 像素 → 文字:左/中/右 + 上/中/下 / pixels -> words
h = '左 left' if x < 54 else ('右 right' if x > 106 else '中 middle')
v = '上 top' if y < 40 else ('下 bottom' if y > 80 else '中 middle')
return h + ', ' + v
while True:
seen = 0
for c in range(1, cam.max_color() + 1): # 色 1~7 / colours 1~7
p = cam.xy(c)
if p and p[0]: # X = 0 表示沒找到 / X = 0 means not found
seen += 1
print('色 colour', c, ':', p[0], p[1], where(p[0], p[1]))
hub.display.number(seen) # 主機螢幕顯示看到幾色 / show how many colours are seen
if Button.LEFT in hub.buttons.pressed():
# 數量放在模式 6,要多等約 30 ms,所以按鍵才讀 / counts are in mode 6 (30 ms slower): read on demand
print('16 色裡找到', cam.colors_found(), '色 / colours found; 色 1 有', cam.count(1), '塊 / blobs of colour 1')
print('---')
wait(500)Step by step
cam.max_color(): mode 12 gives 7 colours on SPIKE, so the loop runs colour 1~7.cam.xy(c)returns (X, Y); X = 0 means not found and is skipped.where()turns pixels into words: X < 54 left, > 106 right; Y < 40 top, > 80 bottom.cam.colors_found()andcam.count(1)are in mode 6 (about 30 ms slower), so they are read only when you press the button.
What you should see: lines like 色 colour 1 : 35 20 左 left, 上 top, a --- line between rounds; the hub screen number = colours seen.
Common problems
- A colour never shows? → it was not taught, or the light changed a lot since; teach it again.
- Need the area? → use mode 13 (4 colours + area) or 14 (2 colours + width, height, area).
Example 3: what colour is at the spot?
What it does: reads hue and saturation of spot S1, decides the colour, lights the hub in that colour and prints the colour name.
Camera mode: camera mode 2 Spot colour, option 2 Hue + saturation = mode number 22 (7 spots on SPIKE). S1 is the cross marked 1 on the camera screen.
You need: SPIKE hub, camera in port C, red, yellow, green, blue, white and black bricks.
Download: ex03_spot_color.py + MBC_UniCam.py
Pybricks code
# 神攝手 × Pybricks(SPIKE)範例 3:座標取色判斷顏色
# SharpShot × Pybricks (SPIKE) example 3: what colour is at the spot?
# 需要把相機選單切到 相機模式 2 座標取色 ・子選項 ② 色相+飽和 =模式編號 22(SPIKE 給 7 點)
# Camera menu: camera mode 2 Spot colour, option 2 Hue + saturation = mode number 22 (7 spots)
# 準備:取樣點 S1 的位置看相機螢幕上標 1 的十字 / spot S1 is the cross marked 1 on the camera screen
# 接線 Wiring:相機 → C 孔 / camera → port C
from pybricks.hubs import PrimeHub
from pybricks.parameters import Port, Color
from pybricks.tools import wait
from MBC_UniCam import UniCam, color_name
hub = PrimeHub()
cam = UniCam(Port.C, mode=22)
# 顏色名稱 → 主機燈號 / colour name -> hub light
LIGHT = {'red': Color.RED, 'orange': Color.ORANGE, 'yellow': Color.YELLOW, 'green': Color.GREEN,
'cyan': Color.CYAN, 'blue': Color.BLUE, 'purple': Color.VIOLET, 'white': Color.WHITE, 'black': Color.NONE}
NAME = {'red': '紅', 'orange': '橙', 'yellow': '黃', 'green': '綠', 'cyan': '青',
'blue': '藍', 'purple': '紫', 'white': '白/灰', 'black': '黑'}
LOW_SAT = 15 # 👉 可以改:飽和度比這低就當成白/灰 / below this saturation = white / grey
while True:
p = cam.hsv(1) # 取樣點 S1 的 (色相碼, 飽和度, None) / (hue, sat, None)
if p is None: # 讀不到相機 / no camera
hub.light.on(Color.NONE)
wait(200)
continue
h, s = p[0], p[1]
name = color_name(h)
if s < LOW_SAT and name != 'black':
name = 'white' # 顏色很淡:當成白/灰 / very pale: white / grey
hub.light.on(LIGHT[name])
print('S1 色相碼 hue', h, ' 飽和度 sat', s, ' →', NAME[name], name)
wait(200)Step by step
cam.hsv(1): in mode 22 it returns (hue code, saturation, None); value (brightness) needs mode 23.color_name(h): hue code →'red','green'… (red 011 or 220240, orange 1227, yellow 2855, green 5699, cyan 100139, blue 140179, purple 180219, 241248 white/grey, 249255 black).- A "colour" with saturation < 15 is very pale and is treated as white/grey.
What you should see: red brick → red light and S1 色相碼 hue 3 飽和度 sat 72 → 紅 red; white paper → white light; black → light off.
Common problems
- Wrong colour? → look at the printed hue code and change the limits in
color_name()for your bricks (copy the function into your program), or do the white calibration in the camera SET menu. - Many spots at once? → mode 21 gives 14 spot hues:
for i in range(1, 15): print(cam.hue(i)).
Example 4: colour order (WRO)
What it does: WRO tasks often need "remember the order of the coloured blocks". The camera sorts the blobs from left to right; the program waits until it reads the same 4 blocks 5 times in a row, beeps, and shows one letter at a time (R red, G green, B blue, Y yellow…).
Camera mode: camera mode 3 Colour order (no option) = mode number 30; teach every colour that can appear on the field.
You need: SPIKE hub, camera in port C, 4 bricks of different colours in a row.
Download: ex04_color_order.py + MBC_UniCam.py
Pybricks code
# 神攝手 × Pybricks(SPIKE)範例 4:照順序給色號(WRO 任務:記下積木的顏色順序)
# SharpShot × Pybricks (SPIKE) example 4: colour order (WRO: remember the order)
# 需要把相機選單切到 相機模式 3 照順序給色號(沒有子選項)=模式編號 30
# Camera menu: camera mode 3 Colour order (no option) = mode number 30
# 準備:在相機上教會場地上會出現的每一種顏色 / teach every colour that can appear on the field
# 接線 Wiring:相機 → C 孔 / camera → port C
from pybricks.hubs import PrimeHub
from pybricks.parameters import Port
from pybricks.tools import wait
from MBC_UniCam import UniCam, color_name
hub = PrimeHub()
cam = UniCam(Port.C, mode=30)
WANT = 4 # 👉 可以改:場地上有幾塊 / how many blocks to expect
LETTER = {'red': 'R', 'orange': 'O', 'yellow': 'Y', 'green': 'G', 'cyan': 'C',
'blue': 'B', 'purple': 'P', 'white': 'W', 'black': 'K'}
# 連續 5 次讀到一樣的順序才算數(避免剛好有手經過)/ same answer 5 times in a row = stable
last = None
same = 0
names = []
while same < 5:
hues = cam.order() # 由左到右每塊的色相碼 / hue codes from left to right
names = [color_name(h) for h in hues] if hues else []
if len(names) == WANT and names == last:
same += 1
else:
same = 0
last = names
wait(50)
print('順序 order:', names)
hub.speaker.beep()
for n in names: # 一個字母一個字母顯示 / show one letter at a time
hub.display.char(LETTER[n])
wait(700)
hub.display.off()Step by step
cam.order(): hue codes from the left (up to 13 on SPIKE). The hue code is the one of the taught colour, so the same colour always gives the same number.cam.total(): total blocks in the picture (can be more than 13).WANT = 4: the number of blocks on the field; if the count is different or the order still changes, it starts counting again.
What you should see: about half a second after the bricks are in place it beeps and shows R, G, B, Y; the computer prints 順序 order: ['red', 'green', 'blue', 'yellow'].
Common problems
- Never beeps? → the count is not
WANT: check the order numbers drawn on the camera screen; move same-colour things out of the background or raise the target Size. - Two similar colours become one? → make the Hue range smaller when teaching.
Example 5: PD line following + stop at a junction
What it does: PD line following that mostly uses the near offset and turns early with the far offset; stops and beeps at a cross or T junction; if the line leaves the picture it turns back toward the side where it was lost.
Camera mode: camera mode 4 Line, option 1 Black line = mode number 41 (white line 42, colour line 43: only change mode=; colour line = target colour 1).
You need: SPIKE hub, driving base (left wheel E, right wheel F), camera in port C at the front looking down and forward, a black line on white with one cross junction.
Download: ex05_line_follow.py + MBC_UniCam.py
Pybricks code
# 神攝手 × Pybricks(SPIKE)範例 5:PD 循線+路口停車
# SharpShot × Pybricks (SPIKE) example 5: PD line following + stop at a junction
# 需要把相機選單切到 相機模式 4 循線 ・子選項 ① 黑線 =模式編號 41(白線 42、色線 43 寫法一樣,改 mode)
# Camera menu: camera mode 4 Line, option 1 Black line = mode number 41 (42 white / 43 colour line: change mode)
# 準備:相機裝在車頭、斜斜往前下方看線 / mount the camera at the front, looking down and forward
# 接線 Wiring:相機 → C 孔;左輪 E、右輪 F / camera → port C; left wheel E, right wheel F
# 方向:站在車後往前看,線在左=負 / standing behind the robot: line on the left = negative
from pybricks.hubs import PrimeHub
from pybricks.pupdevices import Motor
from pybricks.parameters import Port, Direction
from pybricks.robotics import DriveBase
from pybricks.tools import wait
from MBC_UniCam import UniCam
hub = PrimeHub()
cam = UniCam(Port.C, mode=41)
left = Motor(Port.E, Direction.COUNTERCLOCKWISE)
right = Motor(Port.F)
robot = DriveBase(left, right, 56, 112)
SPEED = 120 # 👉 可以改:前進速度 mm/s / forward speed
KP = 1.5 # 👉 可以改:偏多少就轉多少 / proportional gain
KD = 4.0 # 👉 可以改:偏移變快時多修一點 / derivative gain
last_error = 0
while True:
v = cam.line_all() # 14 個值一次讀 / all 14 line values at once
if v is None: # 讀不到相機:停車 / no camera: stop
robot.stop()
wait(50)
continue
near, far, flags, junction, lost = v[0], v[2], v[5], v[6], v[13]
if junction in (1, 2): # 1 十字、2 T 字路口:停車嗶一聲 / cross or T junction: stop
robot.stop()
hub.speaker.beep()
break
if flags == 0: # 近中遠都沒看到線:往最後離線那一邊找 / line lost: search
robot.drive(0, -120 if lost == 1 else 120)
wait(10)
continue
error = 0.7 * near + 0.3 * far # 近處為主、遠處提早轉彎 / mostly near, a bit of far
turn = KP * error + KD * (error - last_error)
last_error = error
robot.drive(SPEED, turn) # 線在左(負)→ 往左轉 / line on the left → turn left
wait(10)Step by step
cam.line_all(): 14 values at once:[0]near offset,[2]far offset,[5]line flags (near 1 + mid 2 + far 4),[6]junction type (0 none 1 cross 2 T 3 left 4 right 5 end),[13]last lost side (1 left 2 right).error = 0.7 × near + 0.3 × far: line on the left is negative → negative turn = turn left.KD × (error − last error): when the offset changes fast (a curve is coming) it corrects more, so the robot wobbles less.
What you should see: the robot follows the black line and starts turning before a curve; at the cross junction it stops, beeps, and the program ends.
Common problems
- Wobbles a lot? → smaller
KP, biggerKD, or a slowerSPEED. - Runs off in curves? → give the far offset more weight (for example 0.5 / 0.5).
- Stops before the junction? → the cross / T stop passed on a real SPIKE hub; only if it stops too early on your mat, add
cam.junction_dist() < 40to theif junction in (1, 2)test (stop only when the junction is close).
Example 6: find the biggest red triangle
What it does: the camera sorts the objects in the picture from largest to smallest; the program looks from the largest down for a "triangle of target 1 (red)", turns to it and drives up until it reaches the bottom of the picture, then stops and beeps. If it sees none it spins slowly to search. The hub display shows how many objects are in view.
Camera mode: camera mode 5 Object detection, option 1 Object list = mode number 51; teach "target 1" with the object itself (the red triangle) and set its shape to Tri (pick Tri in the Target 1 shape menu after teaching, or call cam.target_shape(1, 3) once). On the bench, teaching with a red card split the triangle (a different red) into several pieces; re-teaching with the triangle itself found it.
You need: SPIKE hub, driving base (left wheel E, right wheel F), camera in port C, a red triangle (card or bricks), plus a few other shapes for fun.
Download: ex06_object_pick.py + MBC_UniCam.py
Pybricks code
# 神攝手 × Pybricks(SPIKE)範例 6:找最大的紅色三角形、轉過去
# SharpShot × Pybricks (SPIKE) example 6: find the biggest red triangle and drive to it
# 需要把相機選單切到 相機模式 5 物體偵測 ・子選項 ① 物體清單=模式編號 51
# Camera menu: camera mode 5 Object detection, option 1 Object list = mode number 51
# 準備:用要找的物體本身(紅色三角形)教「目標 1」,並指定形狀 Tri(相機選單 Target 1 shape,或 cam.target_shape(1, 3))
# teach "target 1" with the red triangle itself and set its shape to Tri (camera menu, or cam.target_shape(1, 3))
# 接線 Wiring:相機 → C 孔 / camera → port C;左輪 E、右輪 F / left wheel E, right wheel F
# 同一個資料夾要有 MBC_UniCam.py / MBC_UniCam.py must be imported too
#
# 物體代碼=色號×10+形狀:目標 1 的三角形=1×10+3=13。物體 1、2、3、4 是面積由大到小,
# 所以從物體 1 往下找,第一個代碼是 13 的就是「最大的紅色三角形」。
# Object code = colour × 10 + shape: target 1 triangle = 13. Objects 1~4 are sorted largest first,
# so the first object with code 13 is the biggest red triangle.
from pybricks.hubs import PrimeHub
from pybricks.pupdevices import Motor
from pybricks.parameters import Port, Direction, Color
from pybricks.robotics import DriveBase
from pybricks.tools import wait
from MBC_UniCam import UniCam, shape_name
hub = PrimeHub()
cam = UniCam(Port.C, mode=51) # 函式庫會自動把相機切到這個模式 / the library switches the camera to this mode
left = Motor(Port.E, Direction.COUNTERCLOCKWISE)
right = Motor(Port.F)
robot = DriveBase(left, right, 56, 112) # 輪徑 56 mm、輪距 112 mm / wheel 56 mm, axle 112 mm
WANT = 1 * 10 + 3 # 👉 可以改:目標 1 × 10 + 形狀 3(三角);正方形改成 14、圓改成 11 / square 14, circle 11
KP = 3 # 👉 可以改:偏 1 像素就多轉幾度/秒 / turn rate per pixel of error
NEAR_Y = 100 # 👉 可以改:物體的 Y ≥ 100(畫面最下面)就算到了 / Y at the bottom of the picture = arrived
def find(want):
# 從最大的物體往下找代碼=want 的,回傳它的 (X, Y);找不到 (0, 0) / largest first; (0, 0) = not found
for i in range(1, cam.max_object() + 1): # SPIKE 模式 51 給 4 個物體 / 4 objects on SPIKE
if cam.object(i) == want:
return cam.object_xy(i)
return (0, 0)
print('要找 looking for: 目標 target', WANT // 10, shape_name(WANT % 10, True), shape_name(WANT % 10))
while True:
n = cam.object_count() # 畫面裡有幾個物體 0~8 / objects in view
if n is None: # 從來沒讀到相機 / camera never answered
robot.stop()
hub.light.on(Color.RED)
wait(100)
continue
hub.display.number(n)
x, y = find(WANT)
if x == 0: # 沒看到紅色三角形:原地慢慢轉著找 / not seen: spin to search
robot.drive(0, 60)
hub.light.on(Color.ORANGE)
elif y >= NEAR_Y: # 到了:停車、嗶一聲 / arrived: stop and beep
robot.stop()
hub.light.on(Color.GREEN)
hub.speaker.beep()
break
else:
error = x - 80 # 畫面中間 X = 80;在右 → 正 → 右轉 / right of centre = positive
robot.drive(80, error * KP)
hub.light.on(Color.BLUE)
wait(20)Step by step
WANT = 1 × 10 + 3: object code = colour × 10 + shape, so a triangle of target 1 is 13. Use 14 for a square, 11 for a circle.cam.object(i): code of object i; object 1 is the largest, so the first match from 1 down is the biggest one. SPIKE mode 51 gives 4 objects (cam.max_object()).cam.object_xy(i): centroid (X, Y) in pixels;X − 80is positive on the right → turn right.cam.object_count(): total objects in view (0~8, may be more than 4).
What you should see: with the red triangle on the left the robot turns left, on the right it turns right, while driving forward; in front of it the robot stops, beeps, and the program ends. A red circle or a blue triangle nearby is ignored.
Common problems
- Keeps spinning? → the camera screen must show
5-1at the top right; look at the "colour shape" label next to each object on the camera screen. If the triangle is read as another shape, setTarget 1 shapeto Tri, or raise ShapeTol in the SET menu. - Too many things in view? → SPIKE only gets the 4 largest; a small red triangle may not make the list, so clear the background.
- Want size and area? → use mode 52:
cam.object_size(i),cam.object_area(i)(2 objects only).
LEGO SPIKE App (Education) Python examples
The official App sees the camera as a "colour sensor". Python reads bytes 0~7 with color_sensor.color(), reflection() and rgbi() (colour, reflection, raw red/green/blue). The reading helper UniCamApp at the top of each example splits them by mode (helper alone: MBC_UniCam_App_helper.py). The helper reads the colour with device.data(port.C)[0], because color_sensor.color() turns 5 into 6. device.data(port.C) gives 8 values: byte 0, byte 1, then 6 int16; bytes 813 are readable, 1415 are not (tested 2026-11-01).
| Helper method | Returns | Modes |
|---|---|---|
update() |
read once (call it every loop first) | all |
cell(c) |
grid cell (X, Y) of colour c: 13/14 use 16×12 (colours 1 |
11~14 |
size_cell(c), colors_found() |
width/height cells (14), colours found (11, 12) | 14 / 11, 12 |
hue(i), sat(i), val(i) |
21: hue of spots 1 |
21~23 |
order() |
(hue codes of the first 6 blocks, total) | 30 |
line() |
(near offset, far offset, angle, junction type, near width) | 41~43 |
max_object(), object_count() |
objects with a code (51 → 3, 52 → 2), objects in view (51) | 51, 52 |
object(i), object_color(i), object_shape(i) |
object code, colour, shape (51: objects 1 |
51, 52 |
object_cell(i) |
8×6 grid cell (X, Y) of objects 1~3 (Y top = small) | 51 |
object_xy(1), object_size(1), object_area(i) |
centroid and box (pixels) of object 1; area % of objects 1, 2 | 52 |
App example 1: turn to follow a colour blob
Camera mode: mode number 13 (Colour tracking 3). Wiring: camera C, left wheel E, right wheel F. Colour 1 is on a 16×12 grid; the centre is 8.5.
Download: ex01_track_turn.py
🟨 Official App Python code
# 神攝手 × 樂高官方 SPIKE App(教育版 Python)範例 1:跟著色塊轉
# SharpShot × LEGO SPIKE App (Education, Python) example 1: turn to follow a colour blob
# 需要把相機選單切到 相機模式 1 顏色追蹤 ・子選項 ③ 標準(XY+面積)=模式編號 13
# Camera menu: camera mode 1 Colour tracking, option 3 Standard (XY + area) = mode number 13
# 準備:先在相機上教「目標 1」的顏色 / teach "target 1" on the camera first
# 接線 Wiring:相機 → C 孔;左輪 E、右輪 F(驅動底座)/ camera → port C; left wheel E, right wheel F
# 用法:SPIKE App 新增「Python」專案,整支貼上 / new Python project in the SPIKE App, paste all of this
# ===== 神攝手 讀值小工具(樂高官方 SPIKE App 3 教育版 Python,照抄就好、不用改)=====
# ===== SharpShot reading helper (LEGO SPIKE App 3, Education, Python: copy as is) =====
# 相機在 SPIKE App 裡是「顏色感應器」:顏色、反射光、原始紅/綠/藍 這 5 個數字(byte 0~7)積木與 Python 都讀得到。
# device.data() 回 8 個值:byte 0、byte 1,之後 6 個 int16(byte 8~13 也讀得到,14~15 讀不到;2026-11-01 上機)。
# 「顏色」改讀 device.data() 的第 1 個值:color_sensor.color() 會把 5 換成 6(樂高的綠色),5 和 6 就分不出來。
# The camera shows up as a colour sensor: colour, reflection, raw red/green/blue (bytes 0~7).
# device.data() gives 8 values: byte 0, byte 1, then 6 int16 (bytes 8~13 readable, 14~15 not; tested 2026-11-01).
# The colour byte is read with device.data(): color_sensor.color() turns 5 into 6 (LEGO green).
# mode = 你在相機選單切的模式編號(相機模式×10+子選項,例 13)/ mode = camera menu mode number
import color_sensor
import device
class UniCamApp:
def __init__(self, port_number, mode):
self.port = port_number
self.mode = mode
self.b = [0] * 8 # byte 0~7
def update(self):
"""讀一次最新資料 / read once"""
c = device.data(self.port)[0] # byte 0(color() 會把 5 換成 6 / color() turns 5 into 6)
r = color_sensor.reflection(self.port) # byte 1
rgb = color_sensor.rgbi(self.port) # 原始紅、綠、藍(各 2 byte)
b = [int(c) & 255, int(r) & 255]
for v in rgb[:3]:
v = int(v) & 0xFFFF # 負數也換回 0~65535
b += [v & 255, v >> 8]
self.b = b
return True
def _w(self, i):
# 原始紅(i=0)、綠(1)、藍(2)的 0~65535 / raw red, green, blue word
return self.b[2 + 2 * i] | (self.b[3 + 2 * i] << 8)
def cell(self, color):
"""顏色追蹤:色 color 的格子 (X 格, Y 格),(0, 0)=沒找到(Y 上=小)
模式 13、14:16×12 格(像素 ≈ 格×10−5);模式 11、12:8×6 格(像素 ≈ 格×20−10),色 1~7"""
m = self.mode
if m in (13, 14):
if color == 1:
return (self.b[0], self.b[1])
return (self.b[2 * color - 2], self.b[2 * color - 1]) # 13:色 2~4;14:色 2
if color == 1:
g = self.b[1]
else:
w = self._w((color - 2) // 2)
g = w // 100 if color % 2 == 0 else w % 100
return (g // 10, g % 10)
def size_cell(self, color):
"""模式 14:色 1、2 的 (寬格, 高格)(16×12 格:寬 ≈ 格×10)"""
w = self._w(color) # 色 1 在原始綠、色 2 在原始藍
return (w & 255, w >> 8)
def colors_found(self):
"""模式 11、12:找到幾色(顏色)"""
return self.b[0]
def hue(self, point):
"""座標取色:取樣點的色相碼 0~255(21:點 1~6;22、23:點 1~3)"""
if self.mode == 21:
return self.b[1 + point]
return self.b[2 * point]
def sat(self, point):
"""飽和度(22、23:點 1~3;22 的點 4 在反射光)"""
if self.mode == 22 and point == 4:
return self.b[1]
return self.b[2 * point + 1]
def val(self, point):
"""明度(23:只有點 1,在反射光;其他點在 byte 8~15)"""
return self.b[1] if point == 1 else None
def order(self):
"""照順序給色號:(左起前 6 塊的色相碼, 總塊數)"""
n = min(6, self.b[0])
return (self.b[2:2 + n], self.b[0])
def line(self):
"""循線:(近處偏移, 遠處偏移, 角度, 路口類型, 近處線寬);偏移、角度 −100~100(線在左=負)"""
return (self._w(0) - 100, self._w(1) - 100, self._w(2) - 100, self.b[0], self.b[1])
# ── 物體偵測(51 物體清單、52 物體詳細):物體代碼=色號×10+形狀,0=沒有;物體 1=面積最大 ──
# ── object detection (51 list, 52 detail): code = colour×10 + shape, 0 = none; object 1 = largest ──
def _obj(self, i, n, modes=(51, 52)):
if self.mode not in modes:
raise ValueError('模式 %d 沒有這個值,要用模式 %s / not available in mode %d, use %s' % (self.mode, modes, self.mode, modes))
if not 1 <= i <= n:
raise ValueError('第幾個物體要填 1~%d / object must be 1~%d' % (n, n))
def max_object(self):
"""byte 0~7 給幾個物體的代碼:51→3、52→2(52 的位置、大小只有物體 1)"""
self._obj(1, 1)
return 3 if self.mode == 51 else 2
def object_count(self):
"""51:畫面裡有幾個物體 0~8(顏色)/ objects in view (colour)"""
self._obj(1, 1, (51,))
return self.b[0]
def object(self, i):
"""物體 i 的代碼(51:物體 1~3;52:物體 1~2)/ object code"""
if self.mode == 51:
self._obj(i, 3)
return self.b[1] if i == 1 else self._w(i - 1) // 100
self._obj(i, 2)
return self.b[0] if i == 1 else self._w(2) & 255
def object_color(self, i):
"""物體 i 的色號 1~8,0=沒有"""
return self.object(i) // 10
def object_shape(self, i):
"""物體 i 的形狀 0~9(沒有物體也是 0)"""
return self.object(i) % 10
def object_cell(self, i):
"""51:物體 i(1~3)的 8×6 格 (X 格, Y 格),(0, 0)=沒有(Y 上=小;像素 ≈ 格×20−10)"""
self._obj(i, 3, (51,))
g = self._w(0) if i == 1 else self._w(i - 1) % 100
return (g // 10, g % 10)
def object_xy(self, i):
"""52:物體 1 的質心 (X, Y) 像素,(0, 0)=沒有"""
self._obj(i, 1, (52,))
return (self._w(0) & 255, self._w(0) >> 8)
def object_size(self, i):
"""52:物體 1 的外框 (寬, 高) 像素"""
self._obj(i, 1, (52,))
return (self._w(1) & 255, self._w(1) >> 8)
def object_area(self, i):
"""52:物體 1、2 的面積(佔畫面 %)"""
self._obj(i, 2, (52,))
return self.b[1] if i == 1 else self._w(2) >> 8
def shape_name(s, zh=False):
"""形狀 0~9 → 'other' 'circle' 'ellipse' 'triangle' 'square' 'rectangle' 'pentagon' 'hexagon' 'star' 'trapezoid'(zh=True 中文)"""
if s is None or not 0 <= s <= 9:
return None
return ('其他', '圓', '橢圓', '三角', '正方', '長方', '五邊', '六邊', '星', '梯形')[s] if zh else ('other', 'circle', 'ellipse', 'triangle', 'square', 'rectangle', 'pentagon', 'hexagon', 'star', 'trapezoid')[s]
def color_name(h):
"""色相碼 → 'red' 'orange' 'yellow' 'green' 'cyan' 'blue' 'purple' 'white' 'black'"""
if h > 248:
return 'black'
if h > 240:
return 'white'
if h < 12 or h >= 220:
return 'red'
if h < 28:
return 'orange'
if h < 56:
return 'yellow'
if h < 100:
return 'green'
if h < 140:
return 'cyan'
if h < 180:
return 'blue'
return 'purple'
# ===== 小工具到這裡結束 / end of helper =====
from hub import port, light_matrix
import motor_pair
import runloop
cam = UniCamApp(port.C, 13) # 模式編號要跟相機選單一樣 / must match the camera menu
BASE = 200 # 👉 可以改:前進速度(度/秒)/ forward speed (deg/s)
K = 40 # 👉 可以改:偏 1 格多轉多少 / turn per grid cell of error
async def main():
motor_pair.pair(motor_pair.PAIR_1, port.E, port.F)
while True:
cam.update()
cx, cy = cam.cell(1) # 色 1 的格子:X 1~16、Y 1~12;(0, 0) = 沒看到 / grid cell
if cx == 0: # 沒看到:原地轉著找 / not seen: spin to search
motor_pair.move_tank(motor_pair.PAIR_1, 120, -120)
light_matrix.write('?')
else:
error = cx - 8.5 # 畫面中間 = 8.5 格;在右 → 正 / centre = 8.5; right = positive
motor_pair.move_tank(motor_pair.PAIR_1, int(BASE + K * error), int(BASE - K * error))
light_matrix.write(str(cx))
await runloop.sleep_ms(30)
runloop.run(main())What you should see: blob on the left → the robot turns left; on the right → turns right; the hub screen shows the X cell of colour 1 (1~16); nothing seen → ? and it spins.
App example 2: PD line following + stop at a junction
Camera mode: mode number 41 (Line 1, black). The official App gets near offset, far offset, angle, junction type and near width (offsets and angle are sent + 100; the helper subtracts it).
Download: ex02_line_follow.py
🟨 Official App Python code
# 神攝手 × 樂高官方 SPIKE App(教育版 Python)範例 2:PD 循線+路口停車
# SharpShot × LEGO SPIKE App (Education, Python) example 2: PD line following + stop at a junction
# 需要把相機選單切到 相機模式 4 循線 ・子選項 ① 黑線 =模式編號 41(白線 42、色線 43 也一樣)
# Camera menu: camera mode 4 Line, option 1 Black line = mode number 41 (42 / 43 work the same)
# 準備:相機裝在車頭、斜斜往前下方看線 / camera at the front, looking down and forward
# 接線 Wiring:相機 → C 孔;左輪 E、右輪 F / camera → port C; left wheel E, right wheel F
# 方向:站在車後往前看,線在左=負 / standing behind the robot: line on the left = negative
# ===== 神攝手 讀值小工具(樂高官方 SPIKE App 3 教育版 Python,照抄就好、不用改)=====
# ===== SharpShot reading helper (LEGO SPIKE App 3, Education, Python: copy as is) =====
# 相機在 SPIKE App 裡是「顏色感應器」:顏色、反射光、原始紅/綠/藍 這 5 個數字(byte 0~7)積木與 Python 都讀得到。
# device.data() 回 8 個值:byte 0、byte 1,之後 6 個 int16(byte 8~13 也讀得到,14~15 讀不到;2026-11-01 上機)。
# 「顏色」改讀 device.data() 的第 1 個值:color_sensor.color() 會把 5 換成 6(樂高的綠色),5 和 6 就分不出來。
# The camera shows up as a colour sensor: colour, reflection, raw red/green/blue (bytes 0~7).
# device.data() gives 8 values: byte 0, byte 1, then 6 int16 (bytes 8~13 readable, 14~15 not; tested 2026-11-01).
# The colour byte is read with device.data(): color_sensor.color() turns 5 into 6 (LEGO green).
# mode = 你在相機選單切的模式編號(相機模式×10+子選項,例 13)/ mode = camera menu mode number
import color_sensor
import device
class UniCamApp:
def __init__(self, port_number, mode):
self.port = port_number
self.mode = mode
self.b = [0] * 8 # byte 0~7
def update(self):
"""讀一次最新資料 / read once"""
c = device.data(self.port)[0] # byte 0(color() 會把 5 換成 6 / color() turns 5 into 6)
r = color_sensor.reflection(self.port) # byte 1
rgb = color_sensor.rgbi(self.port) # 原始紅、綠、藍(各 2 byte)
b = [int(c) & 255, int(r) & 255]
for v in rgb[:3]:
v = int(v) & 0xFFFF # 負數也換回 0~65535
b += [v & 255, v >> 8]
self.b = b
return True
def _w(self, i):
# 原始紅(i=0)、綠(1)、藍(2)的 0~65535 / raw red, green, blue word
return self.b[2 + 2 * i] | (self.b[3 + 2 * i] << 8)
def cell(self, color):
"""顏色追蹤:色 color 的格子 (X 格, Y 格),(0, 0)=沒找到(Y 上=小)
模式 13、14:16×12 格(像素 ≈ 格×10−5);模式 11、12:8×6 格(像素 ≈ 格×20−10),色 1~7"""
m = self.mode
if m in (13, 14):
if color == 1:
return (self.b[0], self.b[1])
return (self.b[2 * color - 2], self.b[2 * color - 1]) # 13:色 2~4;14:色 2
if color == 1:
g = self.b[1]
else:
w = self._w((color - 2) // 2)
g = w // 100 if color % 2 == 0 else w % 100
return (g // 10, g % 10)
def size_cell(self, color):
"""模式 14:色 1、2 的 (寬格, 高格)(16×12 格:寬 ≈ 格×10)"""
w = self._w(color) # 色 1 在原始綠、色 2 在原始藍
return (w & 255, w >> 8)
def colors_found(self):
"""模式 11、12:找到幾色(顏色)"""
return self.b[0]
def hue(self, point):
"""座標取色:取樣點的色相碼 0~255(21:點 1~6;22、23:點 1~3)"""
if self.mode == 21:
return self.b[1 + point]
return self.b[2 * point]
def sat(self, point):
"""飽和度(22、23:點 1~3;22 的點 4 在反射光)"""
if self.mode == 22 and point == 4:
return self.b[1]
return self.b[2 * point + 1]
def val(self, point):
"""明度(23:只有點 1,在反射光;其他點在 byte 8~15)"""
return self.b[1] if point == 1 else None
def order(self):
"""照順序給色號:(左起前 6 塊的色相碼, 總塊數)"""
n = min(6, self.b[0])
return (self.b[2:2 + n], self.b[0])
def line(self):
"""循線:(近處偏移, 遠處偏移, 角度, 路口類型, 近處線寬);偏移、角度 −100~100(線在左=負)"""
return (self._w(0) - 100, self._w(1) - 100, self._w(2) - 100, self.b[0], self.b[1])
# ── 物體偵測(51 物體清單、52 物體詳細):物體代碼=色號×10+形狀,0=沒有;物體 1=面積最大 ──
# ── object detection (51 list, 52 detail): code = colour×10 + shape, 0 = none; object 1 = largest ──
def _obj(self, i, n, modes=(51, 52)):
if self.mode not in modes:
raise ValueError('模式 %d 沒有這個值,要用模式 %s / not available in mode %d, use %s' % (self.mode, modes, self.mode, modes))
if not 1 <= i <= n:
raise ValueError('第幾個物體要填 1~%d / object must be 1~%d' % (n, n))
def max_object(self):
"""byte 0~7 給幾個物體的代碼:51→3、52→2(52 的位置、大小只有物體 1)"""
self._obj(1, 1)
return 3 if self.mode == 51 else 2
def object_count(self):
"""51:畫面裡有幾個物體 0~8(顏色)/ objects in view (colour)"""
self._obj(1, 1, (51,))
return self.b[0]
def object(self, i):
"""物體 i 的代碼(51:物體 1~3;52:物體 1~2)/ object code"""
if self.mode == 51:
self._obj(i, 3)
return self.b[1] if i == 1 else self._w(i - 1) // 100
self._obj(i, 2)
return self.b[0] if i == 1 else self._w(2) & 255
def object_color(self, i):
"""物體 i 的色號 1~8,0=沒有"""
return self.object(i) // 10
def object_shape(self, i):
"""物體 i 的形狀 0~9(沒有物體也是 0)"""
return self.object(i) % 10
def object_cell(self, i):
"""51:物體 i(1~3)的 8×6 格 (X 格, Y 格),(0, 0)=沒有(Y 上=小;像素 ≈ 格×20−10)"""
self._obj(i, 3, (51,))
g = self._w(0) if i == 1 else self._w(i - 1) % 100
return (g // 10, g % 10)
def object_xy(self, i):
"""52:物體 1 的質心 (X, Y) 像素,(0, 0)=沒有"""
self._obj(i, 1, (52,))
return (self._w(0) & 255, self._w(0) >> 8)
def object_size(self, i):
"""52:物體 1 的外框 (寬, 高) 像素"""
self._obj(i, 1, (52,))
return (self._w(1) & 255, self._w(1) >> 8)
def object_area(self, i):
"""52:物體 1、2 的面積(佔畫面 %)"""
self._obj(i, 2, (52,))
return self.b[1] if i == 1 else self._w(2) >> 8
def shape_name(s, zh=False):
"""形狀 0~9 → 'other' 'circle' 'ellipse' 'triangle' 'square' 'rectangle' 'pentagon' 'hexagon' 'star' 'trapezoid'(zh=True 中文)"""
if s is None or not 0 <= s <= 9:
return None
return ('其他', '圓', '橢圓', '三角', '正方', '長方', '五邊', '六邊', '星', '梯形')[s] if zh else ('other', 'circle', 'ellipse', 'triangle', 'square', 'rectangle', 'pentagon', 'hexagon', 'star', 'trapezoid')[s]
def color_name(h):
"""色相碼 → 'red' 'orange' 'yellow' 'green' 'cyan' 'blue' 'purple' 'white' 'black'"""
if h > 248:
return 'black'
if h > 240:
return 'white'
if h < 12 or h >= 220:
return 'red'
if h < 28:
return 'orange'
if h < 56:
return 'yellow'
if h < 100:
return 'green'
if h < 140:
return 'cyan'
if h < 180:
return 'blue'
return 'purple'
# ===== 小工具到這裡結束 / end of helper =====
from hub import port, light_matrix, sound
import motor_pair
import runloop
cam = UniCamApp(port.C, 41)
BASE = 250 # 👉 可以改:前進速度(度/秒)/ forward speed (deg/s)
KP = 3 # 👉 可以改:比例 / proportional gain
KD = 8 # 👉 可以改:微分 / derivative gain
async def main():
motor_pair.pair(motor_pair.PAIR_1, port.E, port.F)
last_error = 0
while True:
cam.update()
near, far, angle, junction, width = cam.line() # 偏移 −100~100、路口類型、線寬 %
if junction in (1, 2): # 1 十字、2 T 字路口:停車嗶一聲 / cross or T: stop
motor_pair.stop(motor_pair.PAIR_1)
await sound.beep(880, 300, 100)
break
if width == 0: # 近處看不到線:往上一次偏的那邊找 / line lost: search
turn = -150 if last_error < 0 else 150
motor_pair.move_tank(motor_pair.PAIR_1, turn, -turn)
else:
error = 0.7 * near + 0.3 * far # 近處為主、遠處提早轉彎 / mostly near, a bit of far
turn = KP * error + KD * (error - last_error)
last_error = error
motor_pair.move_tank(motor_pair.PAIR_1, int(BASE + turn), int(BASE - turn))
light_matrix.write(str(junction))
await runloop.sleep_ms(20)
runloop.run(main())What you should see: the robot follows the line and the hub screen shows the junction type (0 = none); at a cross or T junction it stops and beeps. When the near width is 0 (no line) it turns toward the side of the last error.
Official App block table
Education (SPIKE App 3, yellow hub)
Colour and reflection are 1 byte each; raw red/green/blue are 2 bytes each: "A + B ×256" means A = raw red % 256 (remainder) and B = raw red ÷ 256 (whole number); "A ×100 + B" means split with ÷100. Grid Y: top = small. Spot colour 1: two spot hues per value.
Warning
The official App's "colour" (blocks,
color_sensor.color(), the dashboard) turns the raw value 5 into 6 (LEGO green); other numbers are unchanged. So 5 and 6 cannot be told apart: colour 1 X cell, colours found, junction type 5 (dead end), object count and total blocks. "Reflection" is not affected. In Python readdevice.data(port.C)[0]instead (the example helper already does); blocks cannot avoid it.
| Mode | colour | reflection | raw red | raw green | raw blue |
|---|---|---|---|---|---|
| 1-1 Max colours | colours found | colour 1 X×10+Y (8×6) | colour 2 X×10+Y (8×6) ×100 + colour 3 X×10+Y (8×6) | colour 4 X×10+Y (8×6) ×100 + colour 5 X×10+Y (8×6) | colour 6 X×10+Y (8×6) ×100 + colour 7 X×10+Y (8×6) |
| 1-2 Multi | colours found | colour 1 X×10+Y (8×6) | colour 2 X×10+Y (8×6) ×100 + colour 3 X×10+Y (8×6) | colour 4 X×10+Y (8×6) ×100 + colour 5 X×10+Y (8×6) | colour 6 X×10+Y (8×6) ×100 + colour 7 X×10+Y (8×6) |
| 1-3 Standard | colour 1 X (16 cells) | colour 1 Y (12 cells) | colour 2 X (16 cells) + colour 2 Y (12 cells) ×256 | colour 3 X (16 cells) + colour 3 Y (12 cells) ×256 | colour 4 X (16 cells) + colour 4 Y (12 cells) ×256 |
| 1-4 Fine | colour 1 X (16 cells) | colour 1 Y (12 cells) | colour 2 X (16 cells) + colour 2 Y (12 cells) ×256 | colour 1 width (16 cells) + colour 1 height (12 cells) ×256 | colour 2 width (16 cells) + colour 2 height (12 cells) ×256 |
| 2-1 Hue | — | — | S1 hue + S2 hue ×256 | S3 hue + S4 hue ×256 | S5 hue + S6 hue ×256 |
| 2-2 Hue+Sat | — | S4 sat | S1 hue + S1 sat ×256 | S2 hue + S2 sat ×256 | S3 hue + S3 sat ×256 |
| 2-3 Hue+Sat+Val | — | S1 val | S1 hue + S1 sat ×256 | S2 hue + S2 sat ×256 | S3 hue + S3 sat ×256 |
| 3 Colour order | total blocks | — | block 1 hue + block 2 hue ×256 | block 3 hue + block 4 hue ×256 | block 5 hue + block 6 hue ×256 |
| 4 Line (1/2/3 same) | junction type | near width % | near offset + 100 | far offset + 100 | line angle + 100 |
| 5-1 Object list | object count | object 1 code | object 1 X×10+Y (8×6) | object 2 code ×100 + object 2 X×10+Y (8×6) | object 3 code ×100 + object 3 X×10+Y (8×6) |
| 5-2 Object detail | object 1 code | object 1 area % | object 1 X + object 1 Y ×256 | object 1 width + object 1 height ×256 | object 2 code + object 2 area % ×256 |
Robot Inventor (Robot Inventor App, teal hub)
On Robot Inventor the colour is only 010, reflection up to 126 and raw red/green/blue 0255, so each value holds one number (or "A ×10 + B", split with ÷10). Grid 8×6, Y top = small.
| Mode | colour | reflection | raw red | raw green | raw blue |
|---|---|---|---|---|---|
| 1-1 Max colours | colour 1 X (8 cells) | colour 1 Y (6 cells) | colour 2 X (8 cells) ×10 + colour 2 Y (6 cells) | colour 3 X (8 cells) ×10 + colour 3 Y (6 cells) | colour 4 X (8 cells) ×10 + colour 4 Y (6 cells) |
| 1-2 Multi | colour 1 X (8 cells) | colour 1 Y (6 cells) | colour 2 X (8 cells) ×10 + colour 2 Y (6 cells) | colour 3 X (8 cells) ×10 + colour 3 Y (6 cells) | colour 4 X (8 cells) ×10 + colour 4 Y (6 cells) |
| 1-3 Standard | colour 1 X (8 cells) | colour 1 Y (6 cells) | colour 2 X (8 cells) ×10 + colour 2 Y (6 cells) | colour 3 X (8 cells) ×10 + colour 3 Y (6 cells) | colour 4 X (8 cells) ×10 + colour 4 Y (6 cells) |
| 1-4 Fine | colour 1 X (8 cells) | colour 1 Y (6 cells) | colour 2 X (8 cells) ×10 + colour 2 Y (6 cells) | colour 1 width (8 cells) ×10 + colour 1 height (6 cells) | colour 2 width (8 cells) ×10 + colour 2 height (6 cells) |
| 2-1 Hue | — | S4 hue ÷ 2 | S1 hue | S2 hue | S3 hue |
| 2-2 Hue+Sat | — | S2 sat | S1 hue | S1 sat | S2 hue |
| 2-3 Hue+Sat+Val | — | S2 sat÷10×10 + val÷10 | S1 hue | S1 sat÷10×10 + val÷10 | S2 hue |
| 3 Colour order | — | total blocks | block 1 hue | block 2 hue | block 3 hue |
| 4 Line (1/2/3 same) | junction type | near width % | near offset + 100 | far offset + 100 | line angle + 100 |
| 5-1 Object list | object count | object 1 code | object 1 X×10+Y (8×6) | object 2 code | object 2 X×10+Y (8×6) |
| 5-2 Object detail | object count | object 1 code | object 1 X | object 1 Y | object 1 area % |
Caution
In the official App do not use the "colour is [red]?" block: it compares LEGO's own colour numbers. Always put the number in a variable and compare. Robot Inventor App Python: while the program runs the hub switches to the Education combination (0x26), so use the Education table above and read 6 values with
hub.port.C.device.get(); the Robot Inventor table above is for blocks only.
Please note
- The examples on this page ran on a real SPIKE hub (2026-11-01); KP/KD, area limits and the junction stop are suggestions, print the numbers first and tune on a new mat.
- Changing the camera mode: in Pybricks just change
mode=(the library switches the camera); with the official App use the camera menu. - If the hub disconnects, the hub name at the top left of the camera screen turns red; just run the program again.
Next
- Every value in detail: Value reference
- EV3 examples: EV3 examples