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(), never while True. should_stop() is also injected as a global.
  • cyd.sleep(seconds), never time.sleep: it listens for Stop while waiting, returns False when interrupted, and yields to neighbouring projects. In normal mode it is rewritten to await automatically.
  • A project that listens exits cleanly within 3 seconds. A normal task that ignores Stop is cancelled (finally runs). 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

CallWhat 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.A0NodeMCU 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(). jobs already 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

MethodNormal modeSolo 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