Exercicis pràctics — IoT-Vertebrae

Simulador Python · jordibinefa.github.io/iotv


Com usar el simulador
Obre jordibinefa.github.io/iotv, escriu el codi a l'editor i prem ▶ Executar. Interactua amb els botons i sliders del bessó digital per veure com reacciona el programa.

Important: no escriguis cap línia import — al simulador iotv, time, math i random ja estan disponibles automàticament.

Format d'adreces: "0x0" per a la primera vèrtebra, "0x1" per a la segona (no '0000' — aquest és el format de la Raspberry Pi física).


Funcions de referència ràpida

Digitals:

iotv.dinbit("0x0", "b", N)       # llegeix bit N d'entrada → 0 o 1
iotv.din("0x0", "b")             # llegeix les 8 entrades → enter (0–255)
iotv.doutbit("0x0", "a", N, V)   # escriu bit N de sortida (V = 0 o 1)
iotv.dout("0x0", "a", V)         # escriu les 8 sortides alhora (V = 0–255)

Analògiques:

iotv.ain("0x0", "b", CH)         # llegeix canal CH (1–4) → valor cru
iotv.ain2v(raw)                  # valor cru → volts (−10 a +10 V)
iotv.aout("0x0", "a", CH, raw)   # escriu canal CH amb valor cru
iotv.v2aout(volts)               # volts (0–10 V) → valor cru DAC

Temporització i consola:

time.sleep(S)                    # espera S segons (accepta decimals)
print("text", variable)          # escriu a la consola del simulador

Fórmula de remapejat (de −10..+10 V a qualsevol rang físic):

valor = (volts + 10) / 20 * (màxim − mínim) + mínim

Nivell 0 — Primeres passes

No es requereixen coneixements previs de Python. Cada exercici inclou totes les peces necessàries.


0A — El primer LED

Objectiu: Entendre com s'activa una sortida digital.

El codi següent encén el LED DO0. Completa'l perquè encengui els LEDs DO0, DO2 i DO4 alhora, deixant els altres apagats.

Codi inicial (modifica'l):

iotv.doutbit("0x0", "a", 0, 1)   # encén DO0
time.sleep(5)

Pistes:


0B — Parpelleig (blink)

Objectiu: Entendre els bucles i la temporització.

Tasca: Fes que el LED DO0 parpellegi indefinidament amb 1 segon encès i 1 segon apagat.

Pistes:


0C — Llegir un botó

Objectiu: Llegir una entrada digital i prendre decisions.

Tasca: Escriu un programa que llegeixi el botó DI0 (costat B, bit 0) i mostri per consola "Botó premut" o "Botó no premut" cada 500 ms.

Estructura (omple els buits ___):

while True:
    estat = iotv.dinbit("0x0", "b", ___)
    if estat == 1:
        print(___)
    else:
        print(___)
    time.sleep(___)

Extensió: Fes que el LED DO0 s'encengui quan el botó estigui premut i s'apagui quan no ho estigui.


0D — Seqüència temporitzada

Objectiu: Activar actuadors en ordre amb temporitzadors.

Context: Seqüència d'arrencada d'una màquina industrial: pilot lluminós → motor → ventilador.

Tasca: Escriu un programa que faci la seqüència següent, una sola vegada:

  1. Encén DO0 (pilot d'avís) → espera 2 s
  2. Encén DO1 (motor) sense apagar DO0 → espera 3 s
  3. Encén DO2 (ventilador) sense apagar els anteriors → espera 2 s
  4. Apaga tot → imprimeix "Seqüència completada"

Pista: Encendre DO1 no afecta DO0 — cada bit és independent.


0E — Semàfor

Objectiu: Seqüència cíclica amb múltiples sortides i temporitzadors.

Context: Tres LEDs: vermell (DO0), ambre (DO1), verd (DO2).

Cicle:

Tasca: Escriu el codi complet del semàfor que repeteixi el cicle indefinidament. Imprimeix l'estat actiu a cada canvi.

Pista: A cada pas, apaga tots els LEDs primer amb iotv.doutbit(..., 0) i després encén els que toca.


0F — Comptador de polsos

Objectiu: Detectar el flanc de pujada d'un botó (quan passa de 0 a 1).

Context: Un comptador de peces en una cinta ha de comptar cada vegada que la peça passa pel sensor, no cada cicle que el sensor es manté actiu.

Tasca: Escriu un programa que compti quantes vegades es prem el botó DI0 i mostri el total per consola. El comptador no ha d'incrementar mentre el botó es manté premut.

Estructura (omple els buits ___):

comptador = 0
estat_anterior = ___

while True:
    estat_actual = iotv.dinbit("0x0", "b", 0)

    if estat_actual == 1:
        if estat_anterior == ___:        # flanc de pujada
            comptador = comptador + ___
            print("Total polsos:", ___)

    estat_anterior = ___
    time.sleep(0.05)

Nivell 1 — Automatització bàsica

Es recomana haver completat el Nivell 0 o tenir experiència bàsica en Python.


1A — Interruptor i LED amb detecció de canvi

Context: Un sistema de senyal visual ha de reflectir l'estat d'un interruptor de seguretat. La consola ha de registrar cada canvi, però no inundar-se amb missatges repetits.

Tasca: Escriu un programa que:

  1. Llegeixi contínuament DI0 (costat B, bit 0)
  2. Copiï el seu valor a DO0 (costat A, bit 0)
  3. Imprimeixi "Interruptor ON → LED ON" o "Interruptor OFF → LED OFF" només quan l'estat canviï

Pista: Inicialitza estat_anterior = -1 (valor impossible) per forçar que el primer cicle sempre imprimeixi.


1B — Seqüència de LEDs amb velocitat variable

Context: Panells de senyalització industrial amb efecte serpent per indicar que el sistema és actiu.

Tasca: Fes que els LEDs s'encenguin d'un en un, de DO0 a DO7, amb 200 ms entre cada un, repetint indefinidament.

Ampliació: Controla la velocitat amb el slider AI1: −10 V = lent (500 ms), +10 V = ràpid (50 ms).

Fórmula per a l'ampliació: retard = (10 - volts) / 20 * 0.45 + 0.05


1C — Sensor de llum amb histèresi

Context: Un sistema d'il·luminació automàtica oscil·la si el llindar d'encesa i apagada és el mateix (chattering). L'histèresi resol això amb dos llindars diferents.

Assignació:

Tasca: Encén DO0 quan la llum baixi del 30% i apaga'l quan superi el 40%. Entre 30 i 40%, mantén l'estat actual.

Pista: Necessites una variable llum_encesa = False que recordi l'estat actual entre iteracions.


1D — Control de velocitat per potenciòmetre

Context: Els variadors de freqüència (VFD) industrials reben 0–10 V com a consigna de velocitat. El potenciòmetre és la consigna de l'operari.

Assignació:

Tasca: Llegeix el potenciòmetre, converteix a percentatge i escriu la tensió proporcional al canal AO1. Mostra per consola: Consigna: X.X V → Velocitat: YY%

Fórmules: pct = (volts + 10) / 20 * 100sortida_V = pct / 100 * 10


1E — Mescla de color RGBW

Context: Sistemes d'il·luminació RGBW en línies de muntatge o magatzems, mode manual digital.

Tasca:

  1. Llegeix 4 interruptors del costat B (bits 0–3): R, G, B, W
  2. Copia cada valor al bit corresponent del costat A
  3. Mostra per consola: R:1 G:0 B:1 W:0
  4. Extensió: Si R, G i B estan tots actius, activa W automàticament

Nota: El simulador no suporta and. Usa if niuats per combinar condicions.


1F — Sensor BME280 ambiental

Context: El BME280 mesura temperatura, humitat i pressió. Es simula amb tres sliders analògics amb els rangs físics reals del sensor.

Escales de conversió:

Canal Paràmetre Rang físic Fórmula
AI1 Temperatura −40 a +85 °C T = (v + 10) / 20 * 125 − 40
AI2 Humitat relativa 0 a 100 % H = (v + 10) / 20 * 100
AI3 Pressió atmosfèrica 300 a 1100 hPa P = (v + 10) / 20 * 800 + 300

Tasca: Mostra per consola cada 500 ms: T: 23.4 °C | H: 58 % | P: 1013 hPa

Extensió: Activa alertes visuals:


Nivell 2 — Sistemes de control

Exercicis amb lògica de control real basada en els exemples del simulador.


2A — Cinta transportadora amb parada d'emergència

Context: Una cinta industrial fa ping-pong automàticament entre dos extrems. Un botó d'emergència atura el motor immediatament.

Assignació (vèrtebra digital "0x1"):

Bit Costat Funció
0 A (sortida) Motor avant
1 A (sortida) Motor endarrere
2 A (sortida) Solenoide (dispensador de caixes)
5 B (entrada) Sensor esquerra
6 B (entrada) Sensor dreta
7 B (entrada) Botó emergència

Vèrtebra analògica "0x0": AO1 = velocitat del motor, AI1 = potenciòmetre de velocitat.

Tasca: Partint de l'exemple cinta02-v2, modifica'l perquè:

  1. A l'inici, activi el solenoide 100 ms per dispensar una caixa
  2. Actualitzi la velocitat en temps real llegint el potenciòmetre AI1
  3. Compti i mostri per consola quantes vegades la caixa arriba a cada extrem

2B — Sensor BME280 amb control HVAC

Context: Un sistema de climatització (HVAC) ha de mantenir temperatura i humitat dins d'un rang. Quan superen els llindars, activen actuadors: el ventilador i el deshumidificador.

Usa les mateixes escales de conversió de l'exercici 1F.

Tasca: Escriu un control amb histèresi per a dos paràmetres simultàniament:

Mostra per consola l'estat dels dos actuadors cada vegada que canviïn.


2C — RGBW analògic (dimmer de 4 canals)

Context: Control d'intensitat de llum RGBW via senyals analògics. Cada canal té el seu propi slider de 0 a 10 V.

Assignació:

Tasca:

  1. Llegeix els quatre canals analògics d'entrada
  2. Converteix cada valor al rang 0–10 V: sortida = (volts_entrada + 10) / 2
  3. Escriu cada valor al canal de sortida corresponent
  4. Mostra per consola els quatre valors en percentatge (0–100%)

Extensió: Si la suma de les quatre intensitats supera el 300% del màxim total, redueix-les proporcionalment per no superar el límit de potència del driver.


Nivell 3 — Sistemes industrials complexos

Exercicis basats en exemples complets. Requereixen llegir i entendre codi existent.


3A — Ascensor de 3 plantes: comprensió

Context: L'exemple elevator01 és un controlador complet d'ascensor amb màquina d'estats, encoder analògic de posició, botons de planta i cabina, seguretat de portes i parada d'emergència.

Tasca (comprensió): Executa'l al simulador i respon:

  1. L'ascensor té 6 estats: ST_WAIT, ST_MOVE, ST_OPEN, ST_OPENED, ST_CLOSE, ST_EMERG. Quina condició provoca la transició de ST_MOVE a ST_OPEN?
  2. Per quin motiu el bucle principal no usa time.sleep() de durada llarga?
  3. Quin paper fa el canal analògic AI1 en aquest sistema?
  4. Quin mecanisme impedeix que les portes es tanquin si hi ha un obstacle?

Tasca (modificació): Afegeix un comptador de viatges. Cada vegada que l'ascensor arribi a una planta, incrementa'l i mostra: Viatge nº X: Planta Y


3B — Ascensor: extensió amb memòria de plantes

Context: Continuació de 3A. Un ascensor real acumula totes les peticions rebudes i les serveix totes, no només l'última.

Tasca: El codi elevator01 ja gestiona pending_0, pending_1, pending_2. Analitza com funciona aquest sistema de cues i respon:

  1. Quan s'esborra pending_0? Quan es posa a 1?
  2. Prem simultàniament P0 i P2 mentre l'ascensor és a P1. En quin ordre les serveix i per quin motiu?
  3. Modifica el codi perquè mostri per consola totes les plantes pendents en cada iteració del bucle principal, en format: Pendents: P0=1 P1=0 P2=1

3C — RPi real vs simulador: anàlisi comparativa

Context: El fitxer Head02TestRPi02.py és el codi que correria en una Raspberry Pi física connectada al cap IoT-Vertebrae via bus CAN. Compara'l amb els scripts del simulador.

Tasca (anàlisi):

  1. Al simulador, els canvis es detecten fent polling (preguntant cada X ms). Al codi RPi, com es detecten els canvis sense fer polling?
  2. Quina línia registra el callback de canvis digitals? Quina l'elimina?
  3. Per quin motiu el codi RPi necessita can_on() i can_off()? Quin equivalent té al simulador?
  4. El callback s'executa en un thread separat. Per quin motiu no es pot cridar time.sleep() ni funcions bloquejants dins d'un callback?

Reflexió: Completa la taula comparativa:

Característica Simulador (polling) RPi real (callback)
Latència de resposta depèn del time.sleep() ?
Consum de CPU alt ?
Complexitat del codi baixa ?
Ús recomanat ? ?