CYD Studio › Docs › Writing projects
Writing projects
Projects live on the board under /projects (/sd/projects when an SD card is present). Each project is a single .py file that reaches the board's hardware and runtime through import cyd. The same file runs on all three boards; hardware the board does not have raises a clear error instead of silently misbehaving.
Skeleton
import cyd
from machine import Pin
led = Pin(getattr(cyd, 'D1', 22), Pin.OUT, value=0) # NodeMCU: D1, CYD: GPIO22
try:
while not cyd.should_stop(): # listen to the panel's Stop button
led.value(1)
if not cyd.sleep(0.5): break # interruptible wait (seconds)
led.value(0)
if not cyd.sleep(0.5): break
finally:
led.value(0) # clean up however the project ends
The stop contract
while not cyd.should_stop(), neverwhile True.should_stop()is also injected as a global.cyd.sleep(seconds), nevertime.sleep: it listens for Stop while waiting, returnsFalsewhen interrupted, and yields to neighbouring projects. In normal mode it is rewritten toawaitautomatically.- A project that listens exits cleanly within 3 seconds. A normal task that ignores Stop is cancelled (
finallyruns). A project that never waits locks the shared worker; after 5 seconds the board resets (the panel keeps answering meanwhile). In a thread: reset if it is the only one running, otherwise flagged "stuck" with a force button. print()streams to the panel and to/logs/<project>.log; it survives solo mode too.
The cyd module
| Call | What it does |
|---|---|
cyd.should_stop() · cyd.sleep(s) | The stop contract |
cyd.display(rot=None, bgr=None) | Display (created lazily; fill, fill_rect, text(..., scale), circle). Same API on the OLED, colours collapse to on/off |
cyd.touch() | .touch() → (x, y) or None, .pressed(), .raw() |
cyd.set_display(rot, bgr) · cyd.test_pattern() | Orientation |
cyd.led(r, g, b) · cyd.led_off() | On-board RGB LED (inverted logic) |
cyd.light() | LDR, 0–4095 (higher = darker) |
cyd.beep(freq, ms) | Speaker (GPIO 26) |
cyd.buton(pin, fn=None, aktif=0, sekme_ms=40) | Interrupt-driven button: .basildi() (consumes), .sayac (count), .basili() (held); the IRQ is released when the project ends |
cyd.rota('/path', fn) · cyd.rota_sil() · cyd.rotalar() | The project's web page on the main server |
cyd.dram() | Free internal DRAM in bytes; check before BLE/TLS |
cyd.wifi() | The connected STA interface (use it, never disable it) |
cyd.mount_sd() | Mounts the SD card at /sd, returns the path or None |
cyd.claim_display() | (True, None) or (False, 'owner project') |
cyd.job_name() | This project's name |
cyd.D0 … cyd.D8, cyd.A0 | NodeMCU pin labels |
The project's own web page
Do not open a second HTTP server. For every request outside /api the main server first checks the routes registered by projects: one socket, one loop, same port.
import cyd
count = 0
def page(req): # req: {'yontem', 'yol', 'sorgu', 'govde'}
global count
count += 1
return '<h1>hello %d</h1>' % count # str -> html, dict/list -> json,
# bytes -> raw, (status, type, body) -> custom
cyd.rota('/hello', page)
while not cyd.should_stop():
cyd.sleep(1)
The handler runs on the server's thread: keep it short, never cyd.sleep inside it, keep shared state in plain variables. Routes disappear when the project stops. They show up as links in the panel's Apps row. Sample: yuk_kontrol.py (http://<ip>/yuk).
Buttons: interrupts, not polling
import cyd
b = cyd.buton(22) # GPIO22 <-> GND
while not cyd.should_stop():
if b.basildi():
print('pressed', b.sayac)
cyd.sleep(0.05)
Measured (50 simulated presses next to a display project and a heavy thread): 10 ms polling caught 5/50, the interrupt 50/50.
Don'ts
- No second HTTP server. A second listening socket drained internal DRAM through lwIP buffers and froze the panel (measured). Use
cyd.rota. - Never call
wlan.active(False)/wlan.disconnect(). It takes the panel down too. - Never call
machine.reset().jobsalready manages that. - No endless loops without sleep. In normal mode they stall the neighbours; in a thread Stop resets the board.
- Do not draw on the display from two projects. Data is not corrupted (there is an SPI lock) but they draw over each other;
cyd.claim_display()declares ownership.
Network
| Method | Normal mode | Solo mode |
|---|---|---|
requests.get('http://...') | ✅ | ✅ |
requests.get('https://...') | ❌ ENOMEM | ✅ |
Raw socket | ✅ | ✅ |
ntptime.settime() | ✅ | ✅ |
BLE (bluetooth) | ❌ EIO | ✅ |
requests buffers the body in RAM; send large files in chunks over a raw socket (ornek_sunucuya_yukle.py, 9690 bytes verified with 22 KB of DRAM). Not available: camera module, WireGuard, Node.js / C#.
On the NodeMCU
RAM is tight (~24 KB with the panel up). Avoid long print strings; ship big libraries as precompiled .mpy (mpy-cross bme280.py), because importing the .py source needs a large temporary block for parsing and raises MemoryError.
Last updated 2026-10-17 · This page as Markdown