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.
# 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
# Activar I2C
dtparam=i2c_arm=on
# Instal·lar smbus
sudo apt install -y python3-smbus
can_on() / can_off().
API recomanada per a Head02 (ESP32-S3). Comunicació via bus CAN a 100 kbps. Suporta callbacks asíncrons de canvis d'entrada digital.
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()
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()
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.
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).
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àmetre | Tipus | Descripció |
|---|---|---|
| addr | str | Adreça binària de 4 bits (ex: '0000') |
| side | str | 'A' o 'B' |
| → | str | String binari de 8 caràcters (ex: '10110100'), o 'Error' en timeout |
val = iotv.din('0000', 'B') # ex: '00100100' di1 = bool(int(val[6])) # bit DI1 (posició 6 des de l'esquerra)
Escriu el byte complet (0–255) a un costat de sortida digital. Els 8 LEDs/relès s'actualitzen en un sol missatge CAN.
iotv.dout('0000', 'A', 0xFF) # tots encesos iotv.dout('0000', 'A', 0x00) # tots apagats
Escriu un bit individual de sortida sense afectar els altres. posbyte és 0 (DO0) a 7 (DO7). value és 0 o 1.
for bit in range(8): iotv.doutbit('0000', 'A', bit, 1) time.sleep(0.1) iotv.doutbit('0000', 'A', bit, 0)
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.
iotv.dsetup('0000', 'dout', 'din') iotv.dsetup('0001', 'pwm', 'din')
Llegeix la configuració actual de la vèrtebra digital i retorna un string llegible.
iotv.getdsetup('0000') # 'A:dout, B:din'
Retorna la versió del firmware de la vèrtebra digital (ex: '1.5'), o '0.0' en timeout.
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.
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).
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)
Cancel·la el listener de canvis digitals per a l'adreça indicada. Atura el thread de fons de manera neta.
Cancel·la tots els listeners actius. Es crida automàticament a can_off().
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.
raw = iotv.ain('0000', 'B', 1) volts = iotv.ain2v(raw) # ex: 4.98
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.
valors = iotv.ainBatch('0000', 'B') # [v1, v2, v3, v4], cru volts = [iotv.ain2v(v) for v in valors]
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.
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.
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))
Retorna la versió del firmware de la vèrtebra analògica (ex: '1.4'), o '0.0' en timeout.
Llegeix la configuració de la vèrtebra analògica. Retorna string com 'A:ain, B:aout'.
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
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
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.
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()).
iotv.set_async_adc('0000', 'A', True, 200) # costat A cada 200ms iotv.set_async_adc('0000', 'AB', False) # desactiva els dos costats
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.
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)
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).
Cancel·la tots els listeners ADC asíncrons actius. Es crida automàticament a can_off().
Compatible amb qualsevol versió de cap (Head01 i Head02). Cada crida obre i tanca el bus I2C; no cal inicialitzar un bus global.
import i2c_iotv as iotv print(iotv.dversion('0000')) # 'Digital rib version: 1.2' → '0000000100000010' print(iotv.din('0000', 'B')) # '00100100'
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.
Llegeix les 8 entrades digitals d'un costat via I2C. Retorna un string de 8 bits (ex: '00100100').
val = iotv.din('0000', 'B') # '00100100'
Escriu el byte complet (0–255) a un costat de sortida digital.
Escriu un bit individual de sortida (0–7) sense afectar els altres.
Escriu un valor PWM (0–255) a tots els canals del costat configurat en mode PWM.
Escriu un valor PWM (0–255) a un bit individual del costat configurat en mode PWM.
Configura els dos costats. Modes A: 'ain', 'aout', 'aoutpwm'. Modes B: 'bin', 'bout', 'boutpwm', 'bintouch'. Retorna False si la combinació no és vàlida.
iotv.dsetup('0000', 'aout', 'bin') iotv.dsetup('0000', 'ain', 'bout')
Imprimeix 'Digital rib version: X.Y' i retorna un string binari de 16 bits que codifica la versió.
Imprimeix la configuració actual i retorna un string binari de 8 bits que codifica el mode de cada costat.
iotv.getdsetup('0000') # → imprimeix "A digital output, B digital input" # → retorna '00010010'
Llegeix una entrada analògica. ndac és 1–4. Retorna valor cru de 16 bits (0–26624).
v = iotv.ain2v(iotv.ain('0000', 'A', 4)) # 4.98
Escriu un valor cru (0–4095) a un canal DAC. Per enregistrar a l'EEPROM, suma 4096 al valor (màxim 20.000 escriptures).
iotv.aout('0000', 'B', 4, iotv.v2aout(9.3))
Imprimeix 'Analog rib version: X.Y' i retorna un string binari de 16 bits.
Converteix 0–10 V a valor cru DAC (0–4095). Limita a [0, 4095].
iotv.v2aout(10.0) # → 4095 iotv.v2aout(5.0) # → 2048
Converteix el valor cru ADC (0–26624) a volts (−10.0 a +10.0 V).
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.
| Funció / Característica | RPi (CAN/I2C) | Simulador |
|---|---|---|
| Importació | import can_iotv_v2_2 as iotv | objecte iotv disponible directament (no cal import) |
| Inici del bus | iotv.can_on() / can_off() | iotv.init() / iotv.reset() |
| din | din(addr, side) → string binari | din(addr, side) → string binari (igual) |
| dout | dout(addr, side, value) | dout(addr, side, value) (igual) |
| doutbit | doutbit(addr, side, pos, val) | doutbit(addr, side, pos, val) (igual) |
| dinbit | No existeix | dinbit(addr, side, bit) → int (0 o 1) |
| ain canal | ain(addr, side, ndac) ndac=1–4 | ain(addr, side, ch) ch=1–4 (igual) |
| set_async_adc | set_async_adc(addr, side, enable, ms) (v2.2) | set_async_adc(...) (igual, no-op real) |
| on_ain_change | on_ain_change(addr, side, cb) (v2.2) | on_ain_change(addr, side, cb) (igual) |
| off_ain_change | off_ain_change(addr, side) | off_ain_change(addr, side) (igual) |
| off_all_ain_change | off_all_ain_change() | off_all_ain_change() (igual) |
| ainBatch | ainBatch(addr, side) → llista[4] (cru) | ainBatch(addr, side) → array[4] |
| aoutBatch | No existeix | aoutBatch(addr, side, voltages) |
| v2ain | No existeix | v2ain(voltage) → int (rang ADC entrada) |
| aout2v | No existeix | aout2v(raw) → float (0–10 V) |
| v2aout | v2aout(voltage) | v2aout(voltage) (igual) |
| ain2v | ain2v(raw) | ain2v(raw) (igual) |
| on_din_change | on_din_change(addr, cb) (v2.1) | on_din_change(addr, cb) (igual) |
| off_din_change | off_din_change(addr) | off_din_change(addr) (igual) |
| off_all_din_change | off_all_din_change() | off_all_din_change() (igual) |
| PLCMemory | No existeix | PLCMemory.set(key, val) / .get(key) |
| time.sleep | time.sleep(s) | time.sleep(s) (igual, suporta floats) |
try / except / finally — useu if/else per comprovar valorsclass — no suportat[x for x in ...] — useu bucles for explícitsf-strings amb expressions complexes — useu concatenaciólambda, *args, **kwargs, global, nonlocal, yieldimport — tan sols disponibles: iotv, time, math, random, PLCMemorydef de primer nivell (no niuades)for, while, if/elif/elseappend, len)await per a crides iotv que retornen promesesLlegeix les 8 entrades digitals. Retorna string binari de 8 caràcters. Igual que a CAN/I2C.
Llegeix un bit individual d'entrada (0–7). Exclusiva del simulador.
estat = iotv.dinbit('0000', 'B', 1) # 0 o 1
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.
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).
raw = iotv.ain('0000', 'B', 1) v = iotv.ain2v(raw)
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.
vals = iotv.ainBatch('0000', 'B') # [6656, 13312, 0, 26624] v0 = iotv.ain2v(vals[0])
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.
iotv.aoutBatch('0000', 'A', [0.0, 5.0, 10.0, 2.5])
# 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()
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)