A universal Python library for connecting the HuskyLens AI camera to robotics platforms. Supports both V1 and V2 hardware with I2C and Serial (UART) interfaces. Works with MicroPython (LEGO, ESP32) and CPython (Raspberry Pi). Perfect for LEGO robotics, ESP32 projects, Raspberry Pi vision systems, and any Python-enabled device.
- Features
- Installation
- Quick Start
- Hardware Connection
- Example Projects
- API Reference
- Supported Platforms
- Troubleshooting
- License
- Author
- 🤖 Auto-detection: Automatically detects HuskyLens V1 or V2 hardware
- 🔌 Dual Interface: Supports both I2C and Serial (UART) communication
- 🐍 Dual Platform: Works with MicroPython and CPython (Raspberry Pi)
- 🍓 Raspberry Pi Compatible: Native support for Raspberry Pi I2C and Serial
- 🎯 Full Algorithm Support: All 14 AI algorithms including face recognition, object tracking, pose detection, hand recognition, and more
- 🦾 Extended Detection: Full V2 support with facial landmarks, 21-point hand keypoints, and 17-point body pose
- 💾 Memory Optimized: Efficient bytearrays and data structures for MicroPython
- 🔄 Backward Compatible: Unified
HuskyLens()constructor works with all platforms - 🧩 Clean Architecture: Base class pattern with separate I2C and Serial implementations
- 📦 Platform Agnostic: Works with pybricks-micropython, standard MicroPython, SPIKE, Robot Inventor, and Raspberry Pi CPython
Copy and paste the contents of pyhuskylens/pyhuskylens.py above your script.
The easiest way to install is using ViperIDE:
- Open ViperIDE in your browser
- Connect to your ESP32 device
- Go to Tools → Install package via link
- Enter:
github:antonvh/PyHuskyLens - Click Install
Install from your device (if internet-connected):
import mip
mip.install("github:antonvh/PyHuskyLens")Copy pyhuskylens/pyhuskylens.py to your device's filesystem.
Copy the library file to your project and import directly.
Install from PyPI:
pip install pyhuskylensFor I2C support, install smbus2:
pip install pyhuskylens[i2c]For Serial/UART support, install pyserial:
pip install pyhuskylens[serial]For both I2C and Serial:
pip install pyhuskylens[all]from pyhuskylens import HuskyLens, ALGORITHM_OBJECT_RECOGNITION
# Use I2C bus number (0 or 1, typically 1 on Raspberry Pi)
hl = HuskyLens(1) # Automatically uses HuskyLensI2C_RPi
if hl.knock():
print("Connected to HuskyLens V" + str(hl.version))
# Set algorithm
hl.set_alg(ALGORITHM_OBJECT_RECOGNITION)
# Get detected objects
blocks = hl.get_blocks()
print("Found " + str(len(blocks)) + " objects")
for block in blocks:
print("Object at ({},{}) size {}x{}".format(
block.x, block.y, block.width, block.height))from pyhuskylens import HuskyLens, ALGORITHM_FACE_RECOGNITION
# Use serial port path (typically /dev/ttyUSB0 or /dev/ttyAMA0)
hl = HuskyLens("/dev/ttyUSB0") # Automatically uses HuskyLensSerial_RPi
if hl.knock():
print("Connected!")
hl.set_alg(ALGORITHM_FACE_RECOGNITION)
while True:
blocks = hl.get_blocks(learned=True)
if len(blocks) > 0:
face = blocks[0]
print("Hello, ID: " + str(face.ID))from pyhuskylens import HuskyLens, ALGORITHM_OBJECT_RECOGNITION
# Auto-detects I2C or Serial/UART based on parameter type
# For I2C (ESP32)
from machine import Pin, SoftI2C
i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=100000)
hl = HuskyLens(i2c)
# For SPIKE Prime (port string)
# hl = HuskyLens('E')
# For EV3 (Port object)
# from pybricks.parameters import Port
# hl = HuskyLens(Port.S1)
if hl.knock():
print("Connected to HuskyLens V" + str(hl.version))
# Set algorithm
hl.set_alg(ALGORITHM_OBJECT_RECOGNITION)
# Get detected objects
blocks = hl.get_blocks()
print("Found " + str(len(blocks)) + " objects")
for block in blocks:
print("Object at ({},{}) size {}x{}".format(
block.x, block.y, block.width, block.height))from pyhuskylens import HuskyLens, ALGORITHM_FACE_RECOGNITION
hl = HuskyLens(i2c) # or port string, or Port object
if hl.knock():
hl.set_alg(ALGORITHM_FACE_RECOGNITION)
while True:
blocks = hl.get_blocks(learned=True) # Only learned faces
if len(blocks) > 0:
face = blocks[0]
print("Hello, ID: " + str(face.ID))
# V2 only: Access facial landmarks
if face.type == "FACE":
print("Eyes: ({},{}) ({},{})".format(
face.leye_x, face.leye_y,
face.reye_x, face.reye_y))from pyhuskylens import HuskyLens, ALGORITHM_LINE_TRACKING
hl = HuskyLens(Port.S1) # EV3/Pybricks example
hl.set_alg(ALGORITHM_LINE_TRACKING)
while True:
arrows = hl.get_arrows(learned=True)
if len(arrows) > 0:
arrow = arrows[0]
# Calculate steering based on arrow position
center_offset = arrow.x_head - 160
direction = arrow.direction # Angle in degrees
print("Steer: {} Direction: {}°".format(center_offset, direction))from pyhuskylens import HuskyLens, COLOR_GREEN
hl = HuskyLens(i2c)
# Clear any existing text
hl.clear_text()
# Show text (supports both old and new style)
hl.show_text("Hello Robot!", position=(100, 120))
hl.show_text("Status: OK", x=100, y=140, color=COLOR_GREEN)from pyhuskylens import HuskyLens, ALGORITHM_HAND_RECOGNITION
hl = HuskyLens(i2c)
hl.set_alg(ALGORITHM_HAND_RECOGNITION)
while True:
results = hl.get() # Get all detection data
hands = results['hands'] # Or use HANDS constant
for hand in hands:
# Access 21 hand keypoints
print("Wrist: ({},{})".format(hand.wrist_x, hand.wrist_y))
print("Thumb tip: ({},{})".format(hand.thumb_tip_x, hand.thumb_tip_y))
print("Index tip: ({},{})".format(hand.index_finger_tip_x, hand.index_finger_tip_y))
# Calculate gesture from keypoint positions
thumb_up = hand.thumb_tip_y < hand.thumb_mcp_y
if thumb_up:
print("Thumbs up!")from pyhuskylens import HuskyLens, ALGORITHM_POSE_RECOGNITION
hl = HuskyLens(i2c)
hl.set_alg(ALGORITHM_POSE_RECOGNITION)
while True:
results = hl.get()
poses = results['poses'] # Or use POSES constant
for pose in poses:
# Access 17 body keypoints
print("Nose: ({},{})".format(pose.nose_x, pose.nose_y))
print("Shoulders: ({},{}) ({},{})".format(
pose.lshoulder_x, pose.lshoulder_y,
pose.rshoulder_x, pose.rshoulder_y))
# Calculate if person is standing/sitting based on keypoints
hip_y = (pose.lhip_y + pose.rhip_y) / 2
shoulder_y = (pose.lshoulder_y + pose.rshoulder_y) / 2
standing = abs(hip_y - shoulder_y) > 50
print("Standing" if standing else "Sitting")- HuskyLens V1: I2C address
0x32 - HuskyLens V2: I2C address
0x50 - Connections: SDA, SCL, GND, 5V
# Raspberry Pi Example (uses I2C bus number)
hl = HuskyLens(1) # I2C bus 1 (/dev/i2c-1)
# ESP32 Example
from machine import Pin, SoftI2C
i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=100000)
hl = HuskyLens(i2c)Raspberry Pi I2C Setup:
Enable I2C using raspi-config:
sudo raspi-config
# Select: Interface Options → I2C → YesVerify I2C is working:
sudo apt-get install i2c-tools
i2cdetect -y 1 # Should show 0x32 (V1) or 0x50 (V2)- Baudrate: 9600
- HuskyLens Pin 1 (Green) = Tx → Connect to Robot Rx
- HuskyLens Pin 2 (Blue) = Rx → Connect to Robot Tx
- Also connect GND and 5V
# Raspberry Pi Example (uses serial port path)
hl = HuskyLens("/dev/ttyUSB0") # USB serial adapter
# or
hl = HuskyLens("/dev/ttyAMA0") # Built-in UART (Raspberry Pi 3/4/5)
# ESP32 Example
from machine import UART
uart = UART(1, baudrate=9600, tx=17, rx=16)
hl = HuskyLens(uart)
# EV3 Pybricks Example
from pybricks.iodevices import UARTDevice
from pybricks.parameters import Port
uart = UARTDevice(Port.S1, 9600)
hl = HuskyLens(uart)
# SPIKE Prime Example (port string auto-handles setup)
hl = HuskyLens('E') # Port ERaspberry Pi Serial Setup:
For built-in UART (/dev/ttyAMA0), disable the serial console:
sudo raspi-config
# Select: Interface Options → Serial Port
# "Would you like a login shell accessible over serial?" → No
# "Would you like the serial port hardware enabled?" → Yes
sudo rebootTo connect via UART, use a breakout board or solder the included HuskyLens wires to a spare Wedo2 plug pin 5 and 6 (ID1/ID2 lines).
📖 Full SPIKE/MINDSTORMS wiring tutorial
Additional Resources:
Power the HuskyLens with:
- ✅ USB battery pack (recommended - most stable)
- ✅ 5V from robot (if sufficient current available)
⚠️ SPIKE/51515 with LPF power: Use a 3.3V-5V buck converter on M+ (8V) and M- (GND)
Explore the Projects directory for complete working examples:
- EV3 Face Finder - Face tracking with EV3 Pybricks
- EV3 Line Follower - Line tracking robot
- ESP32 PUPRemote - I2C control with ESP32
- SPIKE Face Tracker - Pan/tilt face tracking
- MINDSTORMS Line Follower - SPIKE Prime line following
Universal constructor with auto-detection. Accepts:
- I2C object (MicroPython): Returns
HuskyLensI2Cinstance - Integer (Raspberry Pi): I2C bus number → Returns
HuskyLensI2C_RPiinstance - String starting with "/dev/" (Raspberry Pi): Serial port path → Returns
HuskyLensSerial_RPiinstance - UART object (MicroPython): Returns
HuskyLensSerialinstance - Port string (SPIKE/Inventor): Returns configured
HuskyLensSerial - Port object (Pybricks): Returns configured
HuskyLensSerial
Raspberry Pi I2C interface using smbus2. Requires pip install smbus2.
Raspberry Pi Serial interface using pyserial. Requires pip install pyserial.
Direct I2C interface constructor (MicroPython).
Direct Serial/UART interface constructor (MicroPython).
knock() → bool
Test connection. Returns True if HuskyLens responds.
get_version() → str or None
Get firmware version (V1 only).
version property → int
Hardware version (1 or 2).
set_alg(algorithm) → bool
Switch to specified algorithm. Returns True on success.
set_multi_alg(*algorithms) → bool
Set 2-5 algorithms simultaneously (V2 only).
ALGORITHM_FACE_RECOGNITION = 1 # Detect and recognize faces
ALGORITHM_OBJECT_TRACKING = 2 # Track moving objects
ALGORITHM_OBJECT_RECOGNITION = 3 # Recognize learned objects
ALGORITHM_LINE_TRACKING = 4 # Detect lines and intersections
ALGORITHM_COLOR_RECOGNITION = 5 # Detect colors
ALGORITHM_TAG_RECOGNITION = 6 # Detect AprilTags
ALGORITHM_OBJECT_CLASSIFICATION = 7 # Classify objects (20 classes)
# V2 Only:
ALGORITHM_OCR = 8 # Optical character recognition
ALGORITHM_LICENSE_RECOGNITION = 9 # License plate recognition
ALGORITHM_QR_CODE_RECOGNITION = 10 # QR code detection
ALGORITHM_BARCODE_RECOGNITION = 11 # Barcode scanning
ALGORITHM_FACE_EMOTION_RECOGNITION = 12 # Detect emotions
ALGORITHM_POSE_RECOGNITION = 13 # Body pose (17 keypoints)
ALGORITHM_HAND_RECOGNITION = 14 # Hand pose (21 keypoints)get_blocks(algorithm=None, ID=None, learned=False) → list[Block]
Get detected blocks (faces, objects, poses, hands).
get_arrows(algorithm=None, ID=None, learned=False) → list[Arrow]
Get detected arrows (lines).
get(algorithm=None, ID=None, learned=False) → dict
Get all detections. Returns:
{
BLOCKS: [Block, ...], # or 'blocks'
ARROWS: [Arrow, ...], # or 'arrows'
FACES: [Face, ...], # or 'faces'
HANDS: [Hand, ...], # or 'hands'
POSES: [Pose, ...], # or 'poses'
FRAME: None # or 'frame' (legacy)
}show_text(text, position=(10, 10), x=None, y=None, color=COLOR_WHITE) → bool
Draw text on HuskyLens screen. Supports both parameter styles:
hl.show_text("Hello", position=(50, 50))
hl.show_text("Hello", x=50, y=50, color=COLOR_GREEN)draw_text(text, x=10, y=10, color=COLOR_WHITE) → bool
Alias for show_text() (backward compatibility).
clear_text() → bool
Clear all text from screen.
draw_rect(x1, y1, x2, y2, color=COLOR_WHITE) → bool
Draw rectangle (V2 only).
clear_rect() → bool
Clear rectangles (V2 only).
COLOR_BLACK = 0
COLOR_WHITE = 1
COLOR_RED = 2
COLOR_GREEN = 3
COLOR_BLUE = 4
COLOR_YELLOW = 5Standard detection block with:
x,y- Center positionwidth,height- DimensionsID- Object ID (if learned)confidence- Detection confidence (V2)name- Object name (V2)content- OCR/QR/Barcode content (V2)learned- Whether object is learnedtype- Detection type ("BLOCK", "FACE", "HAND", "POSE")
Line detection with:
x_tail,y_tail- Line startx_head,y_head- Line endID- Line ID (if learned)learned- Whether line is learneddirection- Angle in degreestype- Always "ARROW"
Extends Block with facial landmarks:
leye_x,leye_y- Left eyereye_x,reye_y- Right eyenose_x,nose_y- Nose tiplmouth_x,lmouth_y- Left mouth cornerrmouth_x,rmouth_y- Right mouth corner
Extends Block with 21 keypoints:
- Wrist:
wrist_x,wrist_y - Thumb:
thumb_cmc_*,thumb_mcp_*,thumb_ip_*,thumb_tip_* - Index:
index_finger_mcp_*,index_finger_pip_*,index_finger_dip_*,index_finger_tip_* - Middle:
middle_finger_mcp_*,middle_finger_pip_*,middle_finger_dip_*,middle_finger_tip_* - Ring:
ring_finger_mcp_*,ring_finger_pip_*,ring_finger_dip_*,ring_finger_tip_* - Pinky:
pinky_mcp_*,pinky_pip_*,pinky_dip_*,pinky_tip_*
Extends Block with 17 body keypoints:
nose_x,nose_yleye_x,leye_y,reye_x,reye_ylear_x,lear_y,rear_x,rear_ylshoulder_*,rshoulder_*lelbow_*,relbow_*lwrist_*,rwrist_*lhip_*,rhip_*lknee_*,rknee_*lankle_*,rankle_*
clamp_int(value, min_val=-100, max_val=100) → int
Clamp value to specified range.
- ✅ Raspberry Pi (3/4/5/Zero 2W) with CPython 3.7+
- ✅ ESP32 with MicroPython
- ✅ LEGO SPIKE Prime / Essential (with MicroPython firmware)
- ✅ LEGO MINDSTORMS Robot Inventor 51515
- ✅ LEGO MINDSTORMS EV3 (with pybricks-micropython)
- ✅ Raspberry Pi Pico / Pico W
- ✅ Any MicroPython board with I2C or UART support
"No device found" or knock() returns False
- ✓ Check I2C address: 0x32 (V1) or 0x50 (V2)
- ✓ Verify UART wiring: Green=Tx, Blue=Rx (check you haven't swapped them)
- ✓ Ensure 5V power is connected
- ✓ Try
hl = HuskyLens(i2c, debug=True)to see communication details
⚠️ Use external USB power instead of motor power⚠️ Check voltage stability (use multimeter)⚠️ For SPIKE/51515: Consider downgrading to firmware 0.4.7
No data returned from get_blocks() or get_arrows()
- ✓ Call
knock()first to verify connection - ✓ Check that algorithm is set with
set_alg() - ✓ Ensure objects are learned (press and hold button on HuskyLens)
- ✓ Check if
learned=Truefilter is too restrictive
- ✓ I2C is generally faster than UART
- ✓ Reduce detection frequency in your loop
- ✓ Use specific filters (
ID=,learned=True) to reduce data - ✓ V2 hardware is generally faster than V1
- I2C is automatically detected when passing an integer (bus number)
- Serial is automatically detected when passing a path starting with "/dev/"
- Requires
smbus2for I2C:pip install pyhuskylens[i2c] - Requires
pyserialfor Serial:pip install pyhuskylens[serial] - Enable I2C/Serial in
raspi-configbefore use - Built-in UART on GPIO 14/15 is
/dev/ttyAMA0(Raspberry Pi 3/4/5)
- Port initialization is handled automatically by
HuskyLens('E') - Wait ~2 seconds after initialization for bootup
- Use
debug=Falseto reduce memory usage
- Use
SoftI2Cif hardware I2C has conflicts - Default I2C pins: SCL=22, SDA=21
- Reduce I2C frequency if communication errors occur:
freq=100000or lower
- UART is the only option (no I2C support)
- Use
UARTDevice(Port.S1, 9600) - Ensure pybricks-micropython v2.0+
- Frame Rate: Typically 10-20 FPS depending on algorithm and platform
- I2C vs UART: I2C generally 2-3x faster than UART
- V1 vs V2: V2 has faster processor and more algorithms
- Memory: Uses efficient bytearrays, suitable for memory-constrained devices
- Detection Range: ~20-200cm depending on object size and algorithm
MIT License - See LICENSE file for details
Anton Vanhoucke
Anton's Mindstorms
Contributing: Pull requests welcome! For major changes, please open an issue first.
Issues: Report bugs or request features via GitHub Issues

