· ·
Raspberry Pi · Python

API Python

Biblioteques Python per controlar l'IoT-Vertebrae des de Raspberry Pi, amb bus CAN (recomanat) o bus I2C. Inclou referència per al simulador en línia.

CAN v2.2 (recomanada) I2C Simulador en línia Raspberry Pi
CAN bus — /boot/firmware/config.txt
# I2C + SPI
dtparam=i2c_arm=on
dtparam=spi=on
# MCP2515 (8 MHz, IRQ GPIO6)
dtoverlay=mcp2515-can0,oscillator=8000000,interrupt=6,spimaxfrequency=1000000
dtoverlay=spi-bcm2835-overlay
# Instal·lar python-can
sudo apt install -y python3-can
I2C — /boot/firmware/config.txt
# Activar I2C
dtparam=i2c_arm=on
# Instal·lar smbus
sudo apt install -y python3-smbus
Nota: Cada crida I2C obre i tanca el bus. No cal can_on() / can_off().
Bus CAN — can_iotv_v2_2.py

API recomanada per a Head02 (ESP32-S3). Comunicació via bus CAN a 100 kbps. Suporta callbacks asíncrons de canvis d'entrada digital.

Ús ràpid:
import can_iotv_v2_2 as iotv

iotv.can_on()
print(iotv.dversion('0000'))   # '1.5'
print(iotv.aversion('0000'))   # '1.4'
iotv.can_off()
⬇ can_iotv_v2_2.py ⬇ can_iotv_v2_1.py ⬇ can_iotv_v2_0.py ⬇ Head02TestRPi02.py
Exemple complet — HelloWorld.py:

Script complet que fa parpellejar la sortida digital DO7 del costat B (compatible amb Raspberry Pi i amb el simulador).

import can_iotv_v2_2 as iotv
import asyncio
from time import sleep

# Vertebra address (4-bit binary string) and side to control — edit these two lines for your setup
DIG_VERT_ADDR = "0100"
SIDE = "B"
led_DO7_on = True

# 'aturat' only exists when this script runs inside the simulator;
# it lets the IDE's Stop button end the loop below cleanly.
# On a real Raspberry Pi this variable is undefined, so it's created here as False,
# and the loop only ends via Ctrl+C (KeyboardInterrupt), handled further down.
if 'aturat' not in globals():
    aturat = False

# Async function required by asyncio; the same code runs unchanged on Raspberry Pi and in the simulator
async def main():
    global led_DO7_on

    print(f"DO7 blinking on {SIDE} rib at {DIG_VERT_ADDR} address vertebra")
    while not aturat:   # keeps blinking until stopped
        if led_DO7_on:
            iotv.doutbit(DIG_VERT_ADDR, "b", 7, 1)   # DO7 on
        else:
            iotv.doutbit(DIG_VERT_ADDR, "b", 7, 0)   # DO7 off
        led_DO7_on = not led_DO7_on
        await asyncio.sleep(0.5)   # non-blocking wait, required inside an async function

if __name__ == "__main__":   # entry point: runs only when the script is executed directly, not on import
    try:
        iotv.can_on()

        # Blocking call that runs main() until it returns or is interrupted
        asyncio.run(main())

    except KeyboardInterrupt:   # Ctrl+C on Raspberry Pi
        iotv.dout(DIG_VERT_ADDR, 'B', 0x00)   # turn off all outputs on side B before exiting
        print("\nProgram stopped by user.")
        sleep(0.1)
        iotv.can_off()
⬇ helloWorld.py
Control del bus
None can_on() RPi

Activa la interfície CAN (can0) a 100 kbps. Executa sudo ip link set up can0 type can bitrate 100000. Cal cridar-la al principi de qualsevol programa.

None can_off() RPi

Atura tots els listeners actius, tanca el bus CAN i executa sudo ifconfig can0 down. El tancament també es fa automàticament en sortir del programa (atexit).

Vèrtebra digital
str din(str addr, str side) CAN

Llegeix les 8 entrades digitals d'un costat. Les entrades arriben en actiu baix des de la vèrtebra; la biblioteca les inverteix i retorna el byte en actiu alt com a string de 8 bits.

ParàmetreTipusDescripció
addrstrAdreça binària de 4 bits (ex: '0000')
sidestr'A' o 'B'
strString binari de 8 caràcters (ex: '10110100'), o 'Error' en timeout
exemple
val = iotv.din('0000', 'B')  # ex: '00100100'
di1 = bool(int(val[6]))        # bit DI1 (posició 6 des de l'esquerra)
None dout(str addr, str side, int value) CAN

Escriu el byte complet (0–255) a un costat de sortida digital. Els 8 LEDs/relès s'actualitzen en un sol missatge CAN.

exemple
iotv.dout('0000', 'A', 0xFF)  # tots encesos
iotv.dout('0000', 'A', 0x00)  # tots apagats
None doutbit(str addr, str side, int posbyte, int value) CAN

Escriu un bit individual de sortida sense afectar els altres. posbyte és 0 (DO0) a 7 (DO7). value és 0 o 1.

exemple
for bit in range(8):
    iotv.doutbit('0000', 'A', bit, 1)
    time.sleep(0.1)
    iotv.doutbit('0000', 'A', bit, 0)
None dsetup(str addr, str modeA, str modeB) CAN

Configura els modes de treball dels dos costats d'una vèrtebra digital. El costat A pot ser 'din', 'dout' o 'pwm'. El costat B afegeix 'touch'. Restriccions: PWM només pot estar a un costat; 'touch' només al costat B.

exemple
iotv.dsetup('0000', 'dout', 'din')
iotv.dsetup('0001', 'pwm', 'din')
str getdsetup(str addr) CAN

Llegeix la configuració actual de la vèrtebra digital i retorna un string llegible.

exemple
iotv.getdsetup('0000')  # 'A:dout, B:din'
str dversion(str addr) CAN

Retorna la versió del firmware de la vèrtebra digital (ex: '1.5'), o '0.0' en timeout.

Canvis d'entrada digital asíncrons (nou a v2.1)
Com funciona: La vèrtebra digital publica missatges espontanis quan detecta un canvi a qualsevol entrada. on_din_change() registra un callback que s'executa en un thread de fons sense fer polling, de manera que no col·lapsa el bus.
None on_din_change(str addr, callable callback) async

Registra un callback per a canvis d'entrada digital espontanis de la vèrtebra a addr. El callback rep tres arguments: addr (int, adreça i2c), val_a (byte del costat A, actiu alt), val_b (byte del costat B, actiu alt). El callback s'executa en un thread de fons (core separat del while True principal).

Thread-safety: No crideu funcions de bloqueig (sleep, can_on/off) dins del callback. Useu una variable compartida per coordinar amb el bucle principal.
exemple complet (Head02TestRPi02)
seq_running = False
last_di1    = None

def on_din_change(addr, val_a, val_b):
    global seq_running, last_di1
    di1 = bool(val_b & 0x02)   # DI1 = bit 1 del costat B
    if di1 == last_di1: return
    last_di1 = di1
    if di1:
        seq_running = True
    else:
        seq_running = False
        iotv.dout('0000', 'A', 0x00)

iotv.can_on()
iotv.dsetup('0000', 'dout', 'din')
iotv.on_din_change('0000', on_din_change)

while True:
    if seq_running:
        # ... lògica de seqüència ...
        time.sleep(0.05)
None off_din_change(str addr) async

Cancel·la el listener de canvis digitals per a l'adreça indicada. Atura el thread de fons de manera neta.

None off_all_din_change() async

Cancel·la tots els listeners actius. Es crida automàticament a can_off().

Vèrtebra analògica
int ain(str addr, str side, int ndac) CAN

Llegeix una entrada analògica. ndac és 1, 2, 3 o 4. Retorna el valor cru de 16 bits (0–26624) corresponent a −10V..+10V, o 'Error' en timeout.

exemple
raw = iotv.ain('0000', 'B', 1)
volts = iotv.ain2v(raw)   # ex: 4.98
list ainBatch(str addr, str side) CAN

Llegeix els 4 canals analògics d'un costat EN UNA SOLA petició CAN i retorna una llista de 4 valors crus (0–26624), índex 0 = canal 1. ain() ja fa aquesta mateixa petició internament i en descarta 3 — cridar-lo un cop per canal fa un round-trip CAN sencer per cadascun. Useu ainBatch() sempre que calgui més d'un canal del mateix costat.

exemple
valors = iotv.ainBatch('0000', 'B')   # [v1, v2, v3, v4], cru
volts = [iotv.ain2v(v) for v in valors]
Important: ain() i ainBatch() no fan mai una conversió ADC en viu — retornen el que la vèrtebra ja tenia en una memòria cau, actualitzada pel seu propi escaneig de fons, independent de quan les crideu. Escriure amb aout() i llegir tot seguit amb ain()/ainBatch() sovint retorna el valor ANTERIOR, no el que acabeu de fixar — no hi ha un temps fix a esperar. El simulador no reprodueix aquest retard (llegeix l'estat instantani), així que el mateix codi pot semblar correcte al simulador i sortir desfasat a la Pi real. Preferiu llegir al començament d'una iteració (no just després d'escriure), o useu el push asíncron (set_async_adc + on_ain_change) si cal reaccionar ràpid a un canvi.
None aout(str addr, str side, int ndac, int value) CAN

Escriu un valor cru (0–4095) a un canal DAC. ndac és 1–4. Useu v2aout() per convertir des de volts. Per enregistrar un valor a l'EEPROM del DAC (default en reset), sumeu 4096 al valor. Limiteu les escriptures EEPROM a menys de 20.000 vegades.

exemple
iotv.aout('0000', 'B', 1, iotv.v2aout(5.0))  # 5V al canal 1
# Enregistrar 9.3V a l'EEPROM del DAC canal 4:
iotv.aout('0000', 'B', 4, 4096 + iotv.v2aout(9.3))
str aversion(str addr) CAN

Retorna la versió del firmware de la vèrtebra analògica (ex: '1.4'), o '0.0' en timeout.

str getasetup(str addr) CAN

Llegeix la configuració de la vèrtebra analògica. Retorna string com 'A:ain, B:aout'.

Conversió de tensió
float ain2v(int ain_value) utilitat

Converteix el valor cru ADC (0–26624) a volts (−10.0 a +10.0 V, 2 decimals).

iotv.ain2v(26624)  # → 10.0
iotv.ain2v(13312)  # → 0.0
iotv.ain2v(0)      # → -10.0
int v2aout(float voltage_0_10) utilitat

Converteix una tensió entre 0 i 10 V al valor cru de 12 bits (0–4095) per al DAC. Els valors fora del rang es clampegen per evitar escriptures accidentals a l'EEPROM del DAC.

iotv.v2aout(10.0)  # → 4095
iotv.v2aout(5.0)   # → 2048
iotv.v2aout(0.0)   # → 0
Lectura ADC asíncrona (nou a v2.2)
Com funciona: La vèrtebra analògica pot publicar sola els 4 valors ADC d'un costat cada period_ms, en lloc de fer polling manual amb ain(). Cal activar-ho amb set_async_adc() abans de registrar el callback amb on_ain_change(), o no arribarà mai res.
None set_async_adc(str addr, str side, bool enable, int period_ms=50) async

Activa o desactiva l'enviament periòdic dels valors ADC d'un costat. side pot ser 'A', 'B' o 'AB' (ambdós alhora). period_ms es limita internament a 10–5000 ms. No bloqueja esperant resposta (fire-and-forget, igual que aout()/dout()).

exemple
iotv.set_async_adc('0000', 'A', True, 200)   # costat A cada 200ms
iotv.set_async_adc('0000', 'AB', False)      # desactiva els dos costats
None on_ain_change(str addr, str side, callable callback) async

Registra un listener passiu per als valors ADC d'un costat concret. A diferència de on_din_change() (una vèrtebra sencera), aquí cal un listener per costat — 'A' o 'B', mai els dos alhora. Requereix haver activat abans set_async_adc(addr, side, True, ...). El callback rep (addr, side, values), on values és una llista de 4 valors crus (0–26624), un per canal — useu ain2v() per convertir-los a volts. Si es torna a cridar amb la mateixa addr/side, el listener anterior es cancel·la automàticament.

exemple
def rebre_valors(addr, side, values):
    volts = [iotv.ain2v(v) for v in values]
    print(f"addr={addr} side={side} -> {volts} V")

iotv.set_async_adc('0000', 'A', True, 200)
iotv.on_ain_change('0000', 'A', rebre_valors)
None off_ain_change(str addr, str side) async

Cancel·la el listener d'un costat concret. Nota: això només atura l'escolta al costat del Raspberry Pi — si voleu que la vèrtebra deixi de trametre, cal cridar també set_async_adc(addr, side, False).

None off_all_ain_change() async

Cancel·la tots els listeners ADC asíncrons actius. Es crida automàticament a can_off().

Bus I2C — i2c_iotv.py

Compatible amb qualsevol versió de cap (Head01 i Head02). Cada crida obre i tanca el bus I2C; no cal inicialitzar un bus global.

Ús ràpid:
import i2c_iotv as iotv

print(iotv.dversion('0000'))  # 'Digital rib version: 1.2' → '0000000100000010'
print(iotv.din('0000', 'B'))    # '00100100'
⬇ i2c_iotv.py
Diferència respecte CAN: A I2C, din() retorna un string de 8 bits directament. No hi ha suport per a callbacks asíncrons (on_din_change). El canal analògic (ndac) és 1–4 igual que a CAN.
Vèrtebra digital
str din(str addr, str side) I2C

Llegeix les 8 entrades digitals d'un costat via I2C. Retorna un string de 8 bits (ex: '00100100').

exemple
val = iotv.din('0000', 'B')  # '00100100'
None dout(str addr, str side, int value) I2C

Escriu el byte complet (0–255) a un costat de sortida digital.

None doutbit(str addr, str side, int posbyte, int value) I2C

Escriu un bit individual de sortida (0–7) sense afectar els altres.

None doutpwm(str addr, str side, int value) I2C

Escriu un valor PWM (0–255) a tots els canals del costat configurat en mode PWM.

None doutbitpwm(str addr, str side, int posbyte, int value) I2C

Escriu un valor PWM (0–255) a un bit individual del costat configurat en mode PWM.

bool dsetup(str addr, str modeA, str modeB) I2C

Configura els dos costats. Modes A: 'ain', 'aout', 'aoutpwm'. Modes B: 'bin', 'bout', 'boutpwm', 'bintouch'. Retorna False si la combinació no és vàlida.

exemple
iotv.dsetup('0000', 'aout', 'bin')
iotv.dsetup('0000', 'ain', 'bout')
str dversion(str addr) I2C

Imprimeix 'Digital rib version: X.Y' i retorna un string binari de 16 bits que codifica la versió.

str getdsetup(str addr) I2C

Imprimeix la configuració actual i retorna un string binari de 8 bits que codifica el mode de cada costat.

exemple
iotv.getdsetup('0000')
# → imprimeix "A digital output, B digital input"
# → retorna '00010010'
Vèrtebra analògica
int ain(str addr, str side, int ndac) I2C

Llegeix una entrada analògica. ndac és 1–4. Retorna valor cru de 16 bits (0–26624).

exemple
v = iotv.ain2v(iotv.ain('0000', 'A', 4))  # 4.98
None aout(str addr, str side, int ndac, int value) I2C

Escriu un valor cru (0–4095) a un canal DAC. Per enregistrar a l'EEPROM, suma 4096 al valor (màxim 20.000 escriptures).

exemple
iotv.aout('0000', 'B', 4, iotv.v2aout(9.3))
str aversion(str addr) I2C

Imprimeix 'Analog rib version: X.Y' i retorna un string binari de 16 bits.

Conversió de tensió
int v2aout(float voltage_0_10) utilitat

Converteix 0–10 V a valor cru DAC (0–4095). Limita a [0, 4095].

iotv.v2aout(10.0)  # → 4095
iotv.v2aout(5.0)   # → 2048
float ain2v(int ainValue) utilitat

Converteix el valor cru ADC (0–26624) a volts (−10.0 a +10.0 V).

Simulador en línia — iotvSim

El simulador de iotvsim.binefa.cat executa codi Python transpilat a JavaScript. L'API és similar però amb diferències importants respecte a les biblioteques RPi.

Diferències respecte CAN/I2C
Funció / CaracterísticaRPi (CAN/I2C)Simulador
Importacióimport can_iotv_v2_2 as iotvobjecte iotv disponible directament (no cal import)
Inici del busiotv.can_on() / can_off()iotv.init() / iotv.reset()
dindin(addr, side) → string binaridin(addr, side) → string binari (igual)
doutdout(addr, side, value)dout(addr, side, value) (igual)
doutbitdoutbit(addr, side, pos, val)doutbit(addr, side, pos, val) (igual)
dinbitNo existeixdinbit(addr, side, bit) → int (0 o 1)
ain canalain(addr, side, ndac) ndac=1–4ain(addr, side, ch) ch=1–4 (igual)
set_async_adcset_async_adc(addr, side, enable, ms) (v2.2)set_async_adc(...) (igual, no-op real)
on_ain_changeon_ain_change(addr, side, cb) (v2.2)on_ain_change(addr, side, cb) (igual)
off_ain_changeoff_ain_change(addr, side)off_ain_change(addr, side) (igual)
off_all_ain_changeoff_all_ain_change()off_all_ain_change() (igual)
ainBatchainBatch(addr, side) → llista[4] (cru)ainBatch(addr, side) → array[4]
aoutBatchNo existeixaoutBatch(addr, side, voltages)
v2ainNo existeixv2ain(voltage) → int (rang ADC entrada)
aout2vNo existeixaout2v(raw) → float (0–10 V)
v2aoutv2aout(voltage)v2aout(voltage) (igual)
ain2vain2v(raw)ain2v(raw) (igual)
on_din_changeon_din_change(addr, cb) (v2.1)on_din_change(addr, cb) (igual)
off_din_changeoff_din_change(addr)off_din_change(addr) (igual)
off_all_din_changeoff_all_din_change()off_all_din_change() (igual)
PLCMemoryNo existeixPLCMemory.set(key, val) / .get(key)
time.sleeptime.sleep(s)time.sleep(s) (igual, suporta floats)
Limitacions del transpilador (Python → JavaScript)
El codi Python es transpila a JS. Eviteu:
Sí que funciona:
Vèrtebra digital (simulador)
str din(str addr, str side) sim

Llegeix les 8 entrades digitals. Retorna string binari de 8 caràcters. Igual que a CAN/I2C.

int dinbit(str addr, str side, int bit) sim

Llegeix un bit individual d'entrada (0–7). Exclusiva del simulador.

exemple
estat = iotv.dinbit('0000', 'B', 1)  # 0 o 1
None dout / doutbit / doutpwm / doutbitpwm sim

Mateixa signatura que CAN/I2C. doutpwm(addr, side, val) escriu un valor PWM (0–255) a tots els canals. doutbitpwm(addr, side, bit, val) escriu PWM a un bit individual.

Vèrtebra analògica (simulador)
int ain(str addr, str side, int ch) sim

Llegeix una entrada analògica. ch és 1–4, igual que a CAN/I2C. Retorna valor cru 0–26624 (mateixa escala que la Pi real).

exemple
raw = iotv.ain('0000', 'B', 1)
v = iotv.ain2v(raw)
list ainBatch(str addr, str side) sim

Llegeix els 4 canals analògics d'un costat en una sola crida. Retorna una llista de 4 valors crus (0–26624). Exclusiva del simulador.

exemple
vals = iotv.ainBatch('0000', 'B')  # [6656, 13312, 0, 26624]
v0 = iotv.ain2v(vals[0])
None aoutBatch(str addr, str side, list voltages) sim

Escriu múltiples canals analògics de sortida alhora. voltages és una llista de fins a 4 valors en volts (0–10 V). Exclusiva del simulador.

exemple
iotv.aoutBatch('0000', 'A', [0.0, 5.0, 10.0, 2.5])
Exemple complet per al simulador
Codi generat per a la petició: "Fes un blink de DO5 del costat A de la vèrtebra 0000"
# Blink de DO5 (costat A, vèrtebra 0000)
# Funcions de primer nivell — sense try/except, sense classes

def setup():
    iotv.dsetup('0000', 'output', 'input')

def main():
    setup()
    while True:
        iotv.doutbit('0000', 'A', 5, 1)   # DO5 encès
        time.sleep(0.5)
        iotv.doutbit('0000', 'A', 5, 0)   # DO5 apagat
        time.sleep(0.5)

main()
Equivalent per a RPi (CAN v2.2):
import can_iotv_v2_2 as iotv
import time

iotv.can_on()
iotv.dsetup('0000', 'dout', 'din')
while True:
    iotv.doutbit('0000', 'A', 5, 1)
    time.sleep(0.5)
    iotv.doutbit('0000', 'A', 5, 0)
    time.sleep(0.5)