Skip to main content
Whale Instructor logo

Whale Instructor

An open-source management suite for WhalesBot hardware.

Whale Instructor is an independent open-source project and is not affiliated with, sponsored by, or endorsed by WhalesBot.

It is a Linux-first programming suite for the WhalesBot MC101s controller (sold as WhalesBot E7 Pro / AI S1): a visual Blocks editor that grows with you into real Python and real C, a Python-to-C transpiler, a build pipeline for any modern arm-none-eabi toolchain, a USB uploader, a live debug panel, and a searchable reference of the device API.

All project-authored code is GPL-3.0-or-later; the small runtime your program is built against is LGPL-3.0-or-later. Programs you write with Whale Instructor stay yours — see LICENSE_EXCEPTION.md.

How it fits together

Blocks  ──►  Python (whale dialect)  ──►  py2c.py  ──►  user_main.c ─┐
                                                                     ├─► build_tc.py ──► APP_N.bin ──► whale_cli.py upload
C (advanced, editable) ──────────────────────────────────────────────┘
  • Blocks (beginners): drag-and-drop blocks for motors, sensors, sounds, displays, line following and more — the full block set of the vendor's desktop app. They generate exactly the Python dialect below.
  • Settings: the default hardware profile is the WhalesBot E7 Pro / AI S1 kit (3 motors, 2 touch sensors, 1 infrared, 1 five-in-1 grayscale, 2 emotion LED screens), so the toolbox only shows what kids actually have. Additional kit/add-on hardware can be enabled there at any time. Port dropdowns on the blocks always offer every port — kids simply pick the port they plugged the part into (it is like Lego: re-plug freely, no settings needed). By default the IDE runs in simple mode: one Build + Upload button and no compiler choice. Show advanced controls in the settings enables the compiler selector (only shown when more than one arm-none-eabi-gcc is installed) and a plain Build button; the preferred compiler is remembered.
  • Python: plain, readable Python (from whale import ...). Build compiles it through the transpiler, with line-numbered errors.
  • C: an editable C editor. Take C from Python translates your current Blocks/Python program into C right in the tab (transpiled fresh, never a stale artifact), you can edit it and build it directly; building from Blocks/Python warns before overwriting your hand-edited C.
  • Debug: drive any motor at -100..100 and read the sensors live (over USB: infrared, touch, ambient light, sound, flame, magnetic, ultrasonic, color, single grayscale, and a single reflectance channel of the 5-in-1; over Bluetooth all five 5-in-1 channels), without writing a program. Sensor rows stream readings until you stop them.
  • Help: the complete device API with signatures and descriptions — click a function to insert its call.
  • Run live (Chrome/Edge or the desktop app): run your Blocks or Python program live over Bluetooth — see below.

The IDE speaks 19 languages: English, Deutsch, Español, norsk bokmål, svenska, dansk, suomi, français, italiano, português, Nederlands, polski, русский, 中文(简体), 日本語, 한국어, Türkçe, العربية and עברית (switch in the title bar; Arabic and Hebrew mirror the whole UI right-to-left, including the block workspace).

Linux first (macOS / Windows planned)

This project is developed on Linux and works on Linux today. macOS and Windows support is planned but not implemented yet (the HID uploader currently uses /dev/hidraw; the toolchain finder searches PATH and can be pointed at any arm-none-eabi-gcc via WHALE_INSTRUCTOR_TOOLCHAIN or --gcc=). Everything is stdlib-only Python with argv-list subprocesses, so nothing is Linux-specific by design.

What you need

Everything needed to build controller images ships inside the package: the complete runtime is free software — the Whale Instructor open core (runtime/open_core/, clean-room board/display/control/audio layers), FreeRTOS V9.0.0, ST's StdPeriph drivers, CMSIS and the stock ST startup/device support (runtime/open/), and the user-API layer your program compiles against (runtime/user_api/). No downloads, no vendor material, no extraction step. Build the two runtime archives once:

python3 -m whale_instructor.tools_openlibs

The cross-compiler (free software, fetched automatically)

Building images needs arm-none-eabi-gcc. It's GPL-licensed free software, so the fetch tool can install it for you — it resolves the latest xPack prebuilt release for your platform, unpacks it into ~/.local/share/whale-instructor/toolchains/ (the per-user data dir) and builds auto-discover it there:

whale-fetch-toolchain fetch

Or use your system package manager instead — the build finds it on PATH:

sudo apt install gcc-arm-none-eabi      # Debian/Ubuntu
sudo dnf install arm-none-eabi-gcc      # Fedora
brew install arm-none-eabi-gcc          # macOS

Install (PyPI)

pipx install whale-instructor       # provides whale, whale-ide, whale-fetch-toolchain
python3 -m whale_instructor.tools_openlibs   # build the runtime archives (once)
whale-fetch-toolchain fetch         # free arm-none-eabi-gcc (auto-discovered)
whale-ide                           # open the IDE at http://127.0.0.1:8766/

Instead of fetch toolchain you can install gcc-arm-none-eabi via your system package manager; the build auto-discovers both (and --gcc= / WHALE_INSTRUCTOR_TOOLCHAIN override it).

Quick start (command line, from a source checkout)

# 1. transpile Python (the whale dialect) to C
python3 py2c.py whale_instructor/progs/py_demo.py -o /tmp/user_main.c

# 2. build a controller image (auto-finds a toolchain; newest ~/.local
#    xPack first, then fetched toolchains, then PATH — or pass
#    --gcc=/path/to/arm-none-eabi-gcc)
python3 build_tc.py /tmp/user_main.c 1 /tmp/APP_1.bin

# 3. upload to program slot P1 (stop the vendor app first — it fights
#    over the USB device)
python3 whale_cli.py probe                 # must answer MC1102
python3 whale_cli.py upload /tmp/APP_1.bin 1

The root-level py2c.py / build_tc.py / whale_cli.py / tools_*.py / ide/serve_ide.py are thin shims over the whale_instructor/ package, so these commands work unchanged; the same tools are also available as python3 -m whale_instructor.<module> and, after a PyPI install, as the whale* commands above.

Quick start (IDE)

whale-ide                           # or: python3 ide/serve_ide.py
                                    # http://127.0.0.1:8766/

One pipeline: Blocks/Python → py2c → build_tc.py → upload, with build and upload logs streamed live (the log stays hidden until you open it from the bar at the bottom).

Files: open / save

Programs are ordinary files and live wherever you keep them — the IDE does not keep a program store of its own. Open file… loads any .py/.txt (or .c) file from disk into the matching tab (with the usual unsaved-changes warning), Save as… writes the current program wherever you pick, and plain Save reuses that location. In the browser this uses the File System Access API (Chrome, Edge; other browsers fall back to a download); the desktop app uses native dialogs and writes through the local backend. In static mode (page served without the backend) programs are kept in the browser's localStorage.

Desktop launcher and Quit

whale-ide normally runs in a terminal. To give it a double-clickable icon instead:

whale-ide --install-launcher     # Linux: ~/.local/share/applications/
                                 # Windows: Start Menu shortcut (.cmd)
                                 # macOS: ~/Applications/Whale Instructor.app

The launcher runs the IDE without a terminal window; the Quit button in the IDE's header (backend mode) then shuts the server down cleanly. whale-ide --uninstall-launcher removes the entry again. Re-running install/uninstall when already installed/uninstalled is a no-op.

Standalone app bundle

No Python on the machine? Build a self-contained desktop app with PyInstaller (a build-time tool only — the app itself stays stdlib-only):

python3 -m venv /tmp/bundle-venv
/tmp/bundle-venv/bin/pip install pyinstaller
/tmp/bundle-venv/bin/python tools_build_bundle.py            # one file
/tmp/bundle-venv/bin/python tools_build_bundle.py --onedir   # faster start

Output lands in artifacts/bundle/ (whale-instructor binary, or Whale Instructor.app on macOS). The bundle starts the IDE server and opens the browser like whale-ide does; the Quit button in the IDE shuts it down. Build/upload jobs re-dispatch through the bundle executable, so everything works without a system Python.

Desktop app (Tauri)

desktop/ holds a thin Tauri v2 shell that spawns the same IDE backend and shows it in a native window — with Bluetooth working through a WebBluetooth-compatible bridge, so Run live works there too. The shell also accepts a program file as a launch argument and opens it straight away:

cd desktop && npm install && npm run tauri dev     # development
whale-instructor-desktop rover_demo.py             # installed app

A program opened this way keeps its file identity: plain Save writes back to the opened file, just like after a "Save as…".

Run live over Bluetooth

The toolbar's Run live button (shown only when the browser has WebBluetooth, i.e. Chrome or Edge) runs the current Blocks or Python program live on the controller over Bluetooth, instead of compiling and uploading it. The Python dialect is transpiled to async JavaScript (py2c.py --target js) and executed against the whale BLE runtime (whale_instructor/static/js/whale_ble.js), which speaks the same 20-byte Nordic-UART frame protocol as the vendor's web IDE. The button turns into Stop while a program runs; disconnecting the link stops it too.

The transpile step runs in the browser: whale_instructor/static/js/whalepy.js is a dependency-free implementation of the same dialect (same errors, same output) driven by the machine-readable API dump whale_instructor/static/api.json (regenerate with python3 -m whale_instructor.py2c --dump-api PATH). This is what lets the IDE fall back to static mode when no local backend answers (e.g. the page is served from anywhere as plain files): Blocks → Python → Run live keep working; Build/Upload, the C tab and the program-file server need the app (whale-ide). An Export button downloads the current program instead.

What works live: motors (incl. timed and angle moves, dual motor, the move()/move_time() drive helpers), servos, the LED matrix (symbols, emotions, custom 8x8 patterns), RGB lights, the electromagnet, sounds, and essentially all kit sensors (touch, infrared, 5-in-1 and single grayscale, ultrasonic, ambient light, temperature/humidity, flame, magnetic, sound volume, encoders, color, controller keys). Sensor values are cached and refreshed in the background (~20 Hz), so tight loops don't flood the link.

What does not work over Bluetooth (the runtime raises a clear "not supported over Bluetooth" error, shown in the log pane): the C tab, program upload (USB only — the vendor's own web IDE cannot flash over BLE either), EEPROM, patrol/line following, omni wheels, the digital tube / controller screen / recorder, AI camera/voice modules and the raw digital/analog IO pins. off_LED/off_emotion and line/obstacle detection helpers are emulated client-side (pattern id 0 clears a matrix; detection uses the vendor-matching thresholds whale.grayBlackThreshold / whale.grayWhiteThreshold for the 5-in-1 and graySingle* for the single sensor, plus the tunable whale.irObstacleThreshold), and move() assumes the usual wheel pairing (left = A, right = B — override via whale.wheels).

Live run needs WebBluetooth (Chrome, Edge, or the bundled desktop app; not Firefox) and a machine that already talks BLE. This path is built from the recovered vendor protocol and is partially device-tested (connect + motor runs verified); the USB build+upload pipeline remains the fully validated one — see TODO.md for the remaining checklist.

The Python dialect

from whale import A, set_motor, sleep

def main():
    set_motor(A, 50)
    sleep(3000)
    set_motor(A, 0)
  • entry points: def main() (runs once at boot) and def task(), def task1()task15() (loop tasks; the build adds -DUSER_TASKN)
  • explicit imports only (no star imports), numeric values only (no strings/lists/print — use display_digital_tube(P1, n))
  • if/elif/else, while, for i in range(a, b[, literal step]), break/continue/return/pass, //, %, float /, helper functions, and/or/not, +=/-=///= etc.
  • ~80 device API functions and constants — motors, omni wheels, servos, sounds, LED matrices, grayscale, patrol/line following, sensors, timers, EEPROM. The whole list with descriptions lives in the IDE's Help tab; the machine-readable source of truth is py2c.py (API_HELP), exported to whale_instructor/static/api.json for the browser.
  • display_custom(port, r0..r7) draws an 8x8 LED matrix pattern from eight row bitmasks.

Device objects (ergonomics over the flat API)

The dialect is not Pybricks-compatible — it offers the same kind of ergonomics over whale's own API. On top of the flat functions there is a thin object layer, accepted by both backends (C builds and Run live) and stripped before emission. Everything it spells exists in the flat API; the method names are the flat function with its type prefix dropped:

from whale import Motor, InfraredSensor, B, C, wait

arm = Motor(B)
eye = InfraredSensor(C)

def main():
    arm.set(50)                       # -> set_motor(B, 50)
    arm.set_time(50, 2.5)             # -> set_motor_time(B, 50, 2.5)  (seconds)
    arm.set_angle(50, 90)             # -> set_motor_angle(B, 50, 90)
    while eye.value() > 5:            # -> get_infrared_distance(C)
        wait(20)                      # -> sleep(20)
    arm.off()                         # -> off_motor(B)
    closest = min(eye.value(), 100)   # abs/min/max need no import
  • Docstrings (triple-quoted first statement of a module or function) are allowed and ignored — handy for classroom templates.
  • Keyword arguments work on device API functions and device-object methods (set_motor(speed=50, motor=A)); unknown keywords and kwargs on your own functions are still errors.
  • Constants stay flat (A..D motors, P1..P5 sensor/LED ports, color_red, …) — there are no dotted enum namespaces; attribute access is a transpile error.
  • Device objectsMotor(port), TouchSensor(port), InfraredSensor(port), ColorSensor(port), UltrasonicSensor(port), SoundSensor(port). A constructor returns the port as a typed handle; the handle may also be passed to the matching flat API functions (set_motor(arm, 50) works). Methods are thin aliases of the flat calls — same arguments after the port, same units, no scaling. Misuse is caught at transpile time: unknown methods, handle arithmetic, passing a sensor handle to a motor function, etc.
Object Method Calls
Motor set(speed) set_motor(port, speed)
set_time(speed, seconds) set_motor_time(port, speed, seconds)
set_angle(speed, degrees) set_motor_angle(port, speed, degrees)
off() off_motor(port)
reverse() reverse_motor(port)
speed() get_motor_speed(port)
angle() get_encoder_value(port)
reset_angle() reset_motor_encoder(port)
TouchSensor pressed() touch_switch_pressed(port)
InfraredSensor value() / obstacle() get_infrared_distance / obstacle_infrared_detected
ColorSensor value() / detected(color) color_value / color_detected
UltrasonicSensor value() get_ultrasonic_distance
SoundSensor value() get_sound_volume
  • abs(), min(a, b), max(a, b) are built in (no import); wait(ms) is an alias of sleep(ms).

Still not supported (clear errors, by design): strings beyond docstrings, lists/collections, user classes, DriveBase (no encoder odometry), in, subscripting, tuple unpacking, **, attribute access.

Errors are reported with line numbers, both at transpile time and in the IDE log.

Releases

Releases are built by GitHub Actions on every GitHub release (and on demand via workflow_dispatch):

artifact workflow notes
sdist + wheel on PyPI pypi.yml pipx install whale-instructor; PyPI trusted publishing (no secrets in the repo)
.deb (Debian/Ubuntu) linux-packages.yml arch all; system Python; apt install ./whale-instructor_*.deb
Flatpak linux-packages.yml single-file whale-instructor-<ver>.flatpak bundle (freedesktop 24.08)
desktop bundles desktop.yml Linux .deb + .AppImage, macOS .dmg (Apple silicon + Intel), Windows .msi + .exe — Tauri shell with a PyInstaller backend, self-contained

The Python tests run on every push (ci.yml) across Python 3.9–3.14, plus the node IDE suite and a cargo check of the desktop shell.

Cutting a release

  1. bump version in pyproject.toml, desktop/src-tauri/tauri.conf.json and desktop/package.json, commit, tag: git tag v0.1.0 && git push origin v0.1.0
  2. draft the GitHub release for that tag and publish it — the workflows build and attach all artifacts automatically (PyPI needs the one-time trusted publisher setting on pypi.org: repo johanneswilm/whale-instructor, workflow pypi.yml, environment pypi).

Repository layout

Path Purpose
whale_instructor/ the installable Python package (PyPI: whale-instructor)
whale_instructor/py2c.py Python -> C transpiler (--target js for live Bluetooth), device API tables + reference, device-object sugar layer, --dump-api
whale_instructor/static/js/whalepy.js dependency-free in-browser transpiler of the same dialect (parity enforced by tests)
whale_instructor/static/api.json machine-readable API dump driving whalepy.js (regenerated by make_publish.py)
whale_instructor/build_tc.py build images with any modern arm-none-eabi toolchain (accepts .py directly)
whale_instructor/whale_cli.py USB HID uploader / probe / live motor & sensor CLI
whale_instructor/serve_ide.py + whale_instructor/static/ local web IDE (stdlib server + Blocks/Python/C UI)
whale_instructor/runtime/ LGPL runtime your program is built against: whale_instructor.h, assert_override.c, user_api/ (public API header, app framework, wrappers, patrol engine, syscalls, slot linker scripts)
whale_instructor/runtime/open/ free upstream sources: FreeRTOS, ST StdPeriph, CMSIS, stock ST startup/device support + our configs
whale_instructor/runtime/open_core/ the Whale Instructor open core (wb_*.c board/display/control/audio layers)
whale_instructor/tools_openlibs.py build libwhale_open.a + libwhale_core.a from the free sources
whale_instructor/tools_fetch_toolchain.py fetch arm-none-eabi-gcc (xPack, GPL) into the user data dir
whale_instructor/progs/ sample programs (Python dialect and C)
py2c.py, build_tc.py, ... root shims so the documented commands keep working
desktop/ Tauri v2 desktop shell (native window, Bluetooth bridge, launch-arg open)
tests/ py2c suite (tests/test_py2c.py) + IDE generator checks
make_publish.py builds the publishable publish/ tree
TODO.md open work

Tests

python3 tests/test_py2c.py          # transpiler + JS backend + whalepy parity: 103 checks
node tests/ide_generators.test.js   # IDE blocks -> Python + BLE frames + whalepy: 275 checks

The py2c suite also compiles every sample program with arm-none-eabi-gcc -fsyntax-only against the runtime headers (skipped if no cross-gcc is found; set WHALE_INSTRUCTOR_TOOLCHAIN=/path to point at one). No controller needed.

Operational notes

  • Stop the vendor application (and any Wine bridge) before uploading with whale_cli.py — its periodic USB traffic corrupts in-flight uploads.
  • Uploads go to slots P1..P3; the finalize packet starts the program, so disconnect USB and watch the robot.
  • An image must be built for its target slot (build_tc.py takes the slot as an argument) — never reuse a slot-1 image in P2/P3.

License and trademark

  • Project code: GPL-3.0-or-later (LICENSE); runtime pieces linked into your program: LGPL-3.0-or-later; your own programs: any terms you like (LICENSE_EXCEPTION.md). Third-party notices: THIRD_PARTY_NOTICES.md.
  • Whale Instructor is an independent open-source project and is not affiliated with, sponsored by, or endorsed by WhalesBot. The name "WhalesBot" and the hardware it describes are trademarks of their respective owner; they are mentioned here solely to identify the supported hardware.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

whale_instructor-0.1.0.tar.gz (907.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

whale_instructor-0.1.0-py3-none-any.whl (998.5 kB view details)

Uploaded Python 3

File details

Details for the file whale_instructor-0.1.0.tar.gz.

File metadata

  • Download URL: whale_instructor-0.1.0.tar.gz
  • Upload date:
  • Size: 907.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for whale_instructor-0.1.0.tar.gz
Algorithm Hash digest
SHA256 cf36762df8f7194934cc44012a322b20da5ad0d5213df1d84fe56a97e2ff3d8a
MD5 a26eb59dab2d57a832ef6eca2fa896fe
BLAKE2b-256 b54d46824f21eb3371530214192d86ed038178d69308279fbb1f9632770c601b

See more details on using hashes here.

File details

Details for the file whale_instructor-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for whale_instructor-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 31fdd8f9135f8d9b16b62688607c8cd9c45cd00de48f39bb75348d90864676ab
MD5 5299309f46b96308d2d3be3145da2d8a
BLAKE2b-256 bfe83478b7964c97d44434847557dcaed624041d041010e83099a65268413889

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.1

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page