# Transpiler constraints — IoT-Vertebrae web simulator
## Machine-readable context for AI code generation
### Target: [jordibinefa.github.io/iotv](https://jordibinefa.github.io/iotv)

---

## Purpose

This document is context for an AI assistant (Claude or similar) generating Python code for the IoT-Vertebrae web simulator at `jordibinefa.github.io/iotv`. The simulator transpiles Python to JavaScript before execution. The transpiler is a custom, partial implementation — not Skulpt, not Brython, not PyScript. It supports a strict subset of Python.

**Apply all constraints in this document whenever generating code for this simulator.** Do not apply Python best practices that conflict with these constraints — the transpiler will silently produce wrong results or crash.

---

## Runtime environment

The simulator provides the following globals automatically. **No `import` statements are needed or supported.** Any `import` line is silently ignored or causes an error.

```
iotv        — IoT-Vertebrae API (see API section below)
time        — only time.sleep(seconds: float) is available
math        — standard math module (sqrt, sin, cos, floor, ceil, pi, etc.)
random      — random.random(), random.randint(a, b)
PLCMemory   — key-value store: PLCMemory.set(key, val), PLCMemory.get(key)
```

**Rule:** never emit `import` statements. Never reference `sys`, `os`, `json`, `re`, `collections`, `itertools`, `functools`, or any other Python stdlib module.

---

## Supported Python subset

### Control flow — supported
- `if` / `elif` / `else`
- `while` / `while True`
- `for var in range(...)` — only `range()` iteration, not arbitrary iterables
- `break`, `continue`, `return`
- `pass`

### Functions — partial support
- `def` at **top level only** — no nested functions, no closures
- Default argument values: supported
- Return values: supported
- Recursion: avoid (not tested, stack behavior undefined)

### Variables and types
- `int`, `float`, `str`, `bool` literals: supported
- `list` with `[]` literal and `.append()`, `len()`: supported
- `dict` with `{}` literal and key access `d[k]`: supported with limitations (avoid complex dict comprehensions)
- `None`, `True`, `False`: supported

### Arithmetic and comparison
- `+`, `-`, `*`, `/`, `//`, `%`, `**`: supported
- `==`, `!=`, `<`, `>`, `<=`, `>=`: supported
- Bitwise: `&`, `|`, `^`, `~`, `<<`, `>>`: supported

---

## Unsupported constructs — never emit these

### Logical operators
`and`, `or`, `not` **do not work**. Replace with nested `if` statements.

```python
# WRONG — transpiler silently fails
if a == 1 and b == 1:
    do_thing()

# CORRECT — nested if
if a == 1:
    if b == 1:
        do_thing()
```

```python
# WRONG
if a == 1 or b == 1:
    do_thing()

# CORRECT — duplicate the action
if a == 1:
    do_thing()
if b == 1:
    do_thing()
```

### Classes
`class` definitions are not supported. Use top-level functions with explicit address/state parameters.

```python
# WRONG
class Motor:
    def __init__(self, addr):
        self.addr = addr

# CORRECT
def motor_on(addr):
    iotv.doutbit(addr, "a", 0, 1)
```

### Nested functions and closures
Functions defined inside other functions are not supported.

```python
# WRONG
def outer():
    def inner():   # nested — not supported
        pass

# CORRECT — all defs at top level
def inner():
    pass

def outer():
    inner()
```

### List comprehensions
```python
# WRONG
vals = [iotv.ain("0x0", "b", ch) for ch in range(1, 5)]

# CORRECT
vals = []
for ch in range(1, 5):
    vals.append(iotv.ain("0x0", "b", ch))
```

### Exception handling
`try`, `except`, `finally`, `raise` are not supported.

```python
# WRONG
try:
    v = iotv.ain("0x0", "b", 1)
except:
    v = 0

# CORRECT — check return value directly
v = iotv.ain("0x0", "b", 1)
if v == "Error":
    v = 0
```

### f-strings with expressions
Bare f-strings may work. F-strings with embedded expressions or function calls are unreliable.

```python
# WRONG (unreliable)
print(f"T: {int(temp * 10) / 10} °C")

# CORRECT — use print() with multiple args
print("T:", int(temp * 10) / 10, "°C")
```

### Other unsupported constructs
- `lambda` — use named `def` instead
- `global`, `nonlocal` — all top-level variables are implicitly global in the transpiler; do not emit these keywords
- `yield`, generators — not supported
- `*args`, `**kwargs` — not supported
- `with` / context managers — not supported
- `assert` — not supported
- Decorators (`@`) — not supported
- Walrus operator (`:=`) — not supported
- Type annotations — not supported

### Avoid `round()`
`round()` produces unexpected results in the transpiler. Use integer arithmetic instead:

```python
# WRONG
x = round(val, 1)

# CORRECT
x = int(val * 10) / 10    # 1 decimal place
x = int(val)              # integer
```

---

## IoT-Vertebrae simulator API

### Address format
The simulator uses hex strings: `"0x0"`, `"0x1"`, `"0x2"` ... `"0xf"`

**This differs from the physical Raspberry Pi API** which uses 4-bit binary strings: `'0000'`, `'0001'`, etc. Never mix the two formats in the same codebase.

### Digital vertebra

```python
# Read one input bit — returns 0 or 1
iotv.dinbit(addr: str, side: str, bit: int) -> int

# Read all 8 inputs — returns integer 0-255
iotv.din(addr: str, side: str) -> int

# Write one output bit (val = 0 or 1)
iotv.doutbit(addr: str, side: str, bit: int, val: int) -> None

# Write all 8 outputs at once (val = 0-255)
iotv.dout(addr: str, side: str, val: int) -> None

# PWM output — all channels
iotv.doutpwm(addr: str, side: str, val: int) -> None

# PWM output — single bit
iotv.doutbitpwm(addr: str, side: str, bit: int, val: int) -> None
```

Parameters:
- `addr`: `"0x0"` to `"0xf"`
- `side`: `"a"` or `"b"` (lowercase)
- `bit`: 0–7 (DO0 = bit 0, DO7 = bit 7)

### Analog vertebra

```python
# Read one analog channel — returns raw int
iotv.ain(addr: str, side: str, ch: int) -> int

# Read all 4 channels — returns list of 4 raw ints (simulator-only)
iotv.ainBatch(addr: str, side: str) -> list

# Write one analog channel (raw value)
iotv.aout(addr: str, side: str, ch: int, raw: int) -> None

# Write all 4 channels at once (simulator-only)
iotv.aoutBatch(addr: str, side: str, voltages: list) -> None

# Conversion utilities
iotv.ain2v(raw: int) -> float      # raw (0-26624) → volts (-10.0 to +10.0)
iotv.v2aout(volts: float) -> int   # volts (0-10) → raw DAC (0-4095)
iotv.aout2v(raw: int) -> float     # raw DAC (0-4095) → volts (0-10)  [sim-only]
iotv.v2ain(volts: float) -> int    # volts → raw ADC  [sim-only]
```

Parameters:
- `ch`: 1–4 for CAN/I2C physical API; **0–3** for simulator `ain`/`aout` (off-by-one — see below)

**Critical:** The simulator `ain(addr, side, ch)` uses `ch` = 0–3 (zero-indexed), while the physical CAN/I2C API uses `ch` = 1–4 (one-indexed). In practice, all existing example scripts use 1-indexed even in the simulator and it works — but be aware this is inconsistent. When in doubt, follow the existing examples.

### PLCMemory (simulator-only)
```python
PLCMemory.set(key: str, value)   # store any value under a string key
PLCMemory.get(key: str)          # retrieve value; returns None if not set
```

### Simulator-only functions not available on physical hardware
- `iotv.dinbit()` — exists on sim, not in `can_iotv_v2_1` physical CAN library
- `iotv.ainBatch()`, `iotv.aoutBatch()` — sim only
- `iotv.aout2v()`, `iotv.v2ain()` — sim only
- `PLCMemory` — sim only
- `iotv.on_din_change()` — physical only (sim uses polling)

---

## Code generation rules — summary checklist

Before emitting any code for this simulator, verify:

1. No `import` statements anywhere
2. No `and`, `or`, `not` — use nested `if`
3. No `class` definitions — use top-level `def`
4. No nested `def` inside another `def`
5. No list comprehensions — use `for` + `.append()`
6. No `try`/`except`/`finally`
7. No `f-strings` with expressions — use `print(a, b, c)`
8. No `lambda`
9. No `global` or `nonlocal` keywords
10. No `yield`
11. No `round()` — use `int(x * 10) / 10` or `int(x)`
12. Addresses use `"0x0"` format (not `'0000'`)
13. All `def` at top level only
14. `time.sleep()` is the only time function available
15. `math` and `random` are available without import if needed

---

## Canonical code patterns

### Main loop with state variable
```python
STATE_WAIT = 0
STATE_ACTIVE = 1
STATE_ERROR = 2

state = STATE_WAIT

while True:
    if state == STATE_WAIT:
        entrada = iotv.dinbit("0x0", "b", 0)
        if entrada == 1:
            state = STATE_ACTIVE

    if state == STATE_ACTIVE:
        iotv.doutbit("0x0", "a", 0, 1)
        time.sleep(1)
        iotv.doutbit("0x0", "a", 0, 0)
        state = STATE_WAIT

    time.sleep(0.05)
```

### Rising edge detection (flank detection)
```python
last = 0
count = 0

while True:
    current = iotv.dinbit("0x0", "b", 0)
    if current == 1:
        if last == 0:
            count = count + 1
            print("Count:", count)
    last = current
    time.sleep(0.05)
```

### Analog read with range mapping
```python
while True:
    raw = iotv.ain("0x0", "b", 1)
    volts = iotv.ain2v(raw)                    # -10.0 to +10.0 V
    pct = (volts + 10) / 20 * 100             # 0 to 100 %
    out_v = pct / 100 * 10                    # 0 to 10 V
    iotv.aout("0x0", "a", 1, iotv.v2aout(out_v))
    print("In:", int(volts * 10) / 10, "V  Out:", int(pct), "%")
    time.sleep(0.2)
```

### Helper functions at top level
```python
def all_off():
    iotv.dout("0x0", "a", 0x00)

def led_on(bit):
    iotv.doutbit("0x0", "a", bit, 1)

def led_off(bit):
    iotv.doutbit("0x0", "a", bit, 0)

all_off()
led_on(3)
time.sleep(2)
all_off()
```

---

## Physical Raspberry Pi API — key differences

When generating code for the **physical** Raspberry Pi with `can_iotv_v2_1`:

| Feature | Simulator | Physical RPi |
|---|---|---|
| Import | none needed | `import can_iotv_v2_1 as iotv` |
| Bus init | automatic | `iotv.can_on()` / `iotv.can_off()` |
| Address format | `"0x0"` | `'0000'` |
| Side | `"a"` / `"b"` (lower) | `'A'` / `'B'` (upper) |
| `dinbit()` | ✅ available | ❌ not available |
| `din()` return | int 0-255 | str of 8 bits e.g. `'10110100'` |
| Async change | polling only | `iotv.on_din_change(addr, callback)` |
| `ainBatch` | ✅ | ❌ |
| `PLCMemory` | ✅ | ❌ |
| `and`/`or`/`not` | ❌ | ✅ |
| `class` | ❌ | ✅ |
| `try`/`except` | ❌ | ✅ |
| `import` | ❌ | ✅ |
