Document per al professor. Conté les solucions completes i comentades de tots els exercicis.
iotv.doutbit("0x0", "a", 0, 1)
iotv.doutbit("0x0", "a", 2, 1)
iotv.doutbit("0x0", "a", 4, 1)
time.sleep(5)
Alternativa amb màscara de bits (bits 0, 2, 4 = 0b00010101 = 0x15 = 21):
iotv.dout("0x0", "a", 0b00010101)
time.sleep(5)
Punt clau: dout envia un sol missatge CAN per a totes 8 sortides alhora — més eficient que tres crides a doutbit.
while True:
iotv.doutbit("0x0", "a", 0, 1)
time.sleep(1)
iotv.doutbit("0x0", "a", 0, 0)
time.sleep(1)
Punt clau: while True: és el patró universal de bucle infinit en sistemes encastats. Equivalent a void loop() d'Arduino.
Versió bàsica:
while True:
estat = iotv.dinbit("0x0", "b", 0)
if estat == 1:
print("Botó premut")
else:
print("Botó no premut")
time.sleep(0.5)
Versió amb extensió (LED mirall):
while True:
estat = iotv.dinbit("0x0", "b", 0)
iotv.doutbit("0x0", "a", 0, estat) # estat ja és 0 o 1
if estat == 1:
print("Botó premut → LED ON")
else:
print("Botó no premut → LED OFF")
time.sleep(0.5)
Punt clau: dinbit retorna exactament 0 o 1, el mateix que accepta doutbit — es pot passar directament sense cap if.
print("Iniciant seqüència d'arrencada...")
iotv.doutbit("0x0", "a", 0, 1)
print("DO0 — pilot d'avís")
time.sleep(2)
iotv.doutbit("0x0", "a", 1, 1)
print("DO1 — motor")
time.sleep(3)
iotv.doutbit("0x0", "a", 2, 1)
print("DO2 — ventilador")
time.sleep(2)
iotv.dout("0x0", "a", 0x00)
print("Seqüència completada")
Punt clau: iotv.dout("0x0", "a", 0x00) apaga les 8 sortides en una sola crida. En producció, un bloc try/finally garanteix que s'apagui tot fins i tot si el programa s'interromp.
def apagar_tots():
iotv.doutbit("0x0", "a", 0, 0)
iotv.doutbit("0x0", "a", 1, 0)
iotv.doutbit("0x0", "a", 2, 0)
print("=== Semàfor iniciat ===")
while True:
apagar_tots()
iotv.doutbit("0x0", "a", 0, 1)
print("VERMELL")
time.sleep(4)
iotv.doutbit("0x0", "a", 1, 1)
print("VERMELL + AMBRE")
time.sleep(1)
apagar_tots()
iotv.doutbit("0x0", "a", 2, 1)
print("VERD")
time.sleep(3)
apagar_tots()
iotv.doutbit("0x0", "a", 1, 1)
print("AMBRE")
time.sleep(2)
Punt clau: La funció apagar_tots() evita que quedin LEDs encesos per oblit. En automatització industrial, les columnes lluminoses (Werma, Patlite, Banner) funcionen exactament amb aquest patró — tres sortides digitals temporitzades.
comptador = 0
estat_anterior = 0
while True:
estat_actual = iotv.dinbit("0x0", "b", 0)
if estat_actual == 1:
if estat_anterior == 0:
comptador = comptador + 1
print("Total polsos:", comptador)
estat_anterior = estat_actual
time.sleep(0.05)
Punt clau: La detecció de flanc de pujada (0→1) és el patró més usat en comptadors industrials. La variable estat_anterior és la memòria del sistema — equivalent al bit d'estat del bloc CTU (Counter Up) en IEC 61131-3.
estat_anterior = -1
while True:
estat = iotv.dinbit("0x0", "b", 0)
iotv.doutbit("0x0", "a", 0, estat)
if estat != estat_anterior:
if estat == 1:
print("Interruptor ON → LED ON")
else:
print("Interruptor OFF → LED OFF")
estat_anterior = estat
time.sleep(0.05)
Punt clau: estat_anterior = -1 força que el primer cicle sempre imprimeixi l'estat inicial, sigui quin sigui, sense necessitar un if extra.
Versió bàsica:
while True:
for bit in range(8):
iotv.doutbit("0x0", "a", bit, 1)
time.sleep(0.2)
iotv.doutbit("0x0", "a", bit, 0)
Versió amb velocitat variable (ampliació):
while True:
for bit in range(8):
v = iotv.ain2v(iotv.ain("0x0", "b", 1))
retard = (10 - v) / 20 * 0.45 + 0.05
iotv.doutbit("0x0", "a", bit, 1)
time.sleep(retard)
iotv.doutbit("0x0", "a", bit, 0)
Verificació de la fórmula: slider a +10 V → (10−10)/20*0.45+0.05 = 0.05 s; slider a −10 V → (10−(−10))/20*0.45+0.05 = 0.50 s.
llum_encesa = False
while True:
v = iotv.ain2v(iotv.ain("0x0", "b", 1))
pct = (v + 10) / 20 * 100
if llum_encesa == False:
if pct < 30:
iotv.doutbit("0x0", "a", 0, 1)
llum_encesa = True
print("Llum:", int(pct), "% → ENCESA")
else:
if pct > 40:
iotv.doutbit("0x0", "a", 0, 0)
llum_encesa = False
print("Llum:", int(pct), "% → APAGADA")
time.sleep(0.3)
Punt clau: Sense histèresi, la llum parpelleja incontroladament si el sensor s'estabilitza al 31%. La banda morta de 10 punts (30–40%) elimina el chattering. Patró estàndard en termoregulació, climatització i control de pressió.
while True:
raw = iotv.ain("0x0", "b", 1)
v_entrada = iotv.ain2v(raw)
pct = (v_entrada + 10) / 20 * 100
v_sortida = pct / 100 * 10
iotv.aout("0x0", "a", 1, iotv.v2aout(v_sortida))
print("Consigna:", int(v_entrada * 10) / 10, "V → Velocitat:", int(pct), "%")
time.sleep(0.2)
Fórmula simplificada equivalent: v_sortida = (v_entrada + 10) / 2
Punt clau: En un variador de freqüència real, 10 V correspon a la velocitat màxima del motor (p.ex. 1500 rpm a 50 Hz). La vèrtebra analògica pot fer exactament de pont entre la consigna digital i el variador.
Versió bàsica:
while True:
r = iotv.dinbit("0x0", "b", 0)
g = iotv.dinbit("0x0", "b", 1)
b = iotv.dinbit("0x0", "b", 2)
w = iotv.dinbit("0x0", "b", 3)
iotv.doutbit("0x0", "a", 0, r)
iotv.doutbit("0x0", "a", 1, g)
iotv.doutbit("0x0", "a", 2, b)
iotv.doutbit("0x0", "a", 3, w)
print("R:", r, "G:", g, "B:", b, "W:", w)
time.sleep(0.2)
Versió amb blanc automàtic (extensió):
while True:
r = iotv.dinbit("0x0", "b", 0)
g = iotv.dinbit("0x0", "b", 1)
b = iotv.dinbit("0x0", "b", 2)
w = iotv.dinbit("0x0", "b", 3)
if r == 1:
if g == 1:
if b == 1:
w = 1
iotv.doutbit("0x0", "a", 0, r)
iotv.doutbit("0x0", "a", 1, g)
iotv.doutbit("0x0", "a", 2, b)
iotv.doutbit("0x0", "a", 3, w)
print("R:", r, "G:", g, "B:", b, "W:", w)
time.sleep(0.2)
Punt clau: El simulador no suporta and. Els if niuats són l'equivalent correcte. En LEDs RGBW reals, el canal W dona una blancor més eficient que la mescla R+G+B.
Versió bàsica:
while True:
v_t = iotv.ain2v(iotv.ain("0x0", "b", 1))
v_h = iotv.ain2v(iotv.ain("0x0", "b", 2))
v_p = iotv.ain2v(iotv.ain("0x0", "b", 3))
temp = (v_t + 10) / 20 * 125 - 40
hum = (v_h + 10) / 20 * 100
pres = (v_p + 10) / 20 * 800 + 300
print("T:", int(temp * 10) / 10, "°C | H:", int(hum), "% | P:", int(pres), "hPa")
time.sleep(0.5)
Versió amb alertes (extensió):
while True:
v_t = iotv.ain2v(iotv.ain("0x0", "b", 1))
v_h = iotv.ain2v(iotv.ain("0x0", "b", 2))
v_p = iotv.ain2v(iotv.ain("0x0", "b", 3))
temp = (v_t + 10) / 20 * 125 - 40
hum = (v_h + 10) / 20 * 100
pres = (v_p + 10) / 20 * 800 + 300
print("T:", int(temp * 10) / 10, "°C | H:", int(hum), "% | P:", int(pres), "hPa")
if temp > 40:
iotv.doutbit("0x0", "a", 0, 1)
print(" ⚠ TEMPERATURA ALTA")
else:
iotv.doutbit("0x0", "a", 0, 0)
if hum > 80:
iotv.doutbit("0x0", "a", 1, 1)
print(" ⚠ HUMITAT ALTA")
else:
iotv.doutbit("0x0", "a", 1, 0)
if pres < 970:
iotv.doutbit("0x0", "a", 2, 1)
print(" ⚠ PRESSIÓ BAIXA")
else:
iotv.doutbit("0x0", "a", 2, 0)
time.sleep(0.5)
Valors de referència útils per provar: slider AI1 a 0 V → 22.5 °C; slider AI3 a +8.8 V → 1013 hPa (pressió normal a Barcelona).
vertebraA = "0x0"
vertebraD = "0x1"
bMotorA = 0
bMotorB = 1
bSolenoide = 2
bSensorE = 5
bSensorD = 6
bEmergencia = 7
canalVel = 1
print("=== Cinta transportadora ===")
iotv.doutbit(vertebraD, "a", bSolenoide, 1)
time.sleep(0.1)
iotv.doutbit(vertebraD, "a", bSolenoide, 0)
print("Caixa dispensada")
time.sleep(1)
iotv.aout(vertebraA, "a", canalVel, iotv.v2aout(5.0))
direction = 1
iotv.doutbit(vertebraD, "a", bMotorA, 1)
iotv.doutbit(vertebraD, "a", bMotorB, 0)
print("→ Motor endavant")
comptador_dreta = 0
comptador_esquerra = 0
while True:
adc = iotv.ain(vertebraA, "b", canalVel)
speed_v = iotv.ain2v(adc)
speed_dac = (speed_v + 10) / 2
if speed_dac < 0:
speed_dac = 0
if speed_dac > 10:
speed_dac = 10
iotv.aout(vertebraA, "a", canalVel, iotv.v2aout(speed_dac))
emergency = iotv.dinbit(vertebraD, "b", bEmergencia)
if emergency == 1:
print("⚠ EMERGÈNCIA — Motor aturat")
iotv.doutbit(vertebraD, "a", bMotorA, 0)
iotv.doutbit(vertebraD, "a", bMotorB, 0)
time.sleep(0.5)
continue
if direction == 1:
iotv.doutbit(vertebraD, "a", bMotorA, 1)
iotv.doutbit(vertebraD, "a", bMotorB, 0)
if direction != 1:
iotv.doutbit(vertebraD, "a", bMotorA, 0)
iotv.doutbit(vertebraD, "a", bMotorB, 1)
sensorE = iotv.dinbit(vertebraD, "b", bSensorE)
sensorD = iotv.dinbit(vertebraD, "b", bSensorD)
detected = 0
if direction == 1:
if sensorD == 1:
detected = 1
if direction != 1:
if sensorE == 1:
detected = 1
if detected == 1:
direction = direction * -1
if direction == 1:
comptador_esquerra = comptador_esquerra + 1
print("← Esquerra | E:", comptador_esquerra, "D:", comptador_dreta)
if direction != 1:
comptador_dreta = comptador_dreta + 1
print("→ Dreta | E:", comptador_esquerra, "D:", comptador_dreta)
time.sleep(0.01)
ventilador_on = False
deshumid_on = False
while True:
v_t = iotv.ain2v(iotv.ain("0x0", "b", 1))
v_h = iotv.ain2v(iotv.ain("0x0", "b", 2))
temp = (v_t + 10) / 20 * 125 - 40
hum = (v_h + 10) / 20 * 100
# Control temperatura amb histèresi (23–26 °C)
if ventilador_on == False:
if temp > 26:
iotv.doutbit("0x0", "a", 0, 1)
ventilador_on = True
print("Ventilador ON — T:", int(temp * 10) / 10, "°C")
else:
if temp < 23:
iotv.doutbit("0x0", "a", 0, 0)
ventilador_on = False
print("Ventilador OFF — T:", int(temp * 10) / 10, "°C")
# Control humitat amb histèresi (60–70%)
if deshumid_on == False:
if hum > 70:
iotv.doutbit("0x0", "a", 1, 1)
deshumid_on = True
print("Deshumidificador ON — H:", int(hum), "%")
else:
if hum < 60:
iotv.doutbit("0x0", "a", 1, 0)
deshumid_on = False
print("Deshumidificador OFF — H:", int(hum), "%")
time.sleep(0.5)
Punt clau: Dos controls d'histèresi independents al mateix bucle és exactament com funciona un controlador HVAC real: cada paràmetre té la seva banda morta pròpia i el seu actuador independent.
Versió bàsica:
while True:
r = iotv.ain2v(iotv.ain("0x0", "b", 1))
g = iotv.ain2v(iotv.ain("0x0", "b", 2))
b = iotv.ain2v(iotv.ain("0x0", "b", 3))
w = iotv.ain2v(iotv.ain("0x0", "b", 4))
r_out = (r + 10) / 2
g_out = (g + 10) / 2
b_out = (b + 10) / 2
w_out = (w + 10) / 2
iotv.aout("0x0", "a", 1, iotv.v2aout(r_out))
iotv.aout("0x0", "a", 2, iotv.v2aout(g_out))
iotv.aout("0x0", "a", 3, iotv.v2aout(b_out))
iotv.aout("0x0", "a", 4, iotv.v2aout(w_out))
print("R:", int(r_out * 10), "% G:", int(g_out * 10), "% B:", int(b_out * 10), "% W:", int(w_out * 10), "%")
time.sleep(0.2)
Versió amb limitació de potència (extensió):
while True:
r = (iotv.ain2v(iotv.ain("0x0", "b", 1)) + 10) / 2
g = (iotv.ain2v(iotv.ain("0x0", "b", 2)) + 10) / 2
b = (iotv.ain2v(iotv.ain("0x0", "b", 3)) + 10) / 2
w = (iotv.ain2v(iotv.ain("0x0", "b", 4)) + 10) / 2
total = r + g + b + w
limit = 30.0 # 300% del màxim de 10V = 30V total
if total > limit:
factor = limit / total
r = r * factor
g = g * factor
b = b * factor
w = w * factor
print("Limitació aplicada — factor:", int(factor * 100), "%")
iotv.aout("0x0", "a", 1, iotv.v2aout(r))
iotv.aout("0x0", "a", 2, iotv.v2aout(g))
iotv.aout("0x0", "a", 3, iotv.v2aout(b))
iotv.aout("0x0", "a", 4, iotv.v2aout(w))
time.sleep(0.2)
1. Condició de transició ST_MOVE → ST_OPEN:
Quan la distància entre la posició de l'encoder i la posició destí és inferior a MARGIN (0.2 m): if dist < MARGIN: state = ST_OPEN.
2. Per quin motiu no usa time.sleep() llarg al bucle principal:
Perquè necessita reaccionar ràpidament als botons i sensors. Un sleep llarg bloquejarà el sistema fins que acabi, ignorant possibles emergències o pulsacions. El retard de 0.1 s al final és el màxim acceptable.
3. Funció del canal analògic AI1:
Simula l'encoder de posició de la cabina. El valor −10..+10 V es converteix a 0..6 metres (P0=0 m, P1=3 m, P2=6 m). En un ascensor real seria un encoder incremental o absolut.
4. Mecanisme de seguretat de portes:
El sensor DoorSafety (vèrtebra "0x1", costat B, bit 3). Si s'activa durant el tancament (ST_CLOSE), el sistema torna immediatament a ST_OPEN. Simula un fotocel·le o una barra de pressió.
Modificació — comptador de viatges:
Al principi del programa (fora del bucle):
comptador_viatges = 0
Dins del bloc if dist < MARGIN:, just abans de state = ST_OPEN:
comptador_viatges = comptador_viatges + 1
print("Viatge nº", comptador_viatges, ": Planta", current_floor)
1. Quan s'esborra pending_0:
S'esborra (pending_0 = 0) quan l'ascensor arriba a la planta 0 (current_floor == 0 dins de ST_MOVE). Es posa a 1 quan es detecta qualsevol botó de la planta 0 (botó de cabina bCabCall0 o botó de planta bP0Up).
2. Ordre de servei amb P0 i P2 pendents des de P1:
L'ascensor comprova primer les plantes en la direcció actual. Amb direction >= 0 (puja), mira P1, P2 en ordre. Com que ja és a P1, el target serà P2. Quan baixa de P2, la direcció és −1 i cerca P1, P0 — troba P0. L'ordre serà P2 → P0. És l'algoritme SCAN (ascensor de disc dur), que minimitza el recorregut total.
3. Mostrar plantes pendents (modificació):
Al final del bloc if state == ST_WAIT:, just abans del if target_floor >= 0::
print("Pendents: P0=", pending_0, "P1=", pending_1, "P2=", pending_2)
1. Com es detecten els canvis sense polling a la RPi:
Amb un callback asíncron. on_din_change(addr, callback) registra una funció que la biblioteca can_iotv_v2_1 crida automàticament en un thread de fons quan la vèrtebra envia un missatge espontani de canvi. El programa no pregunta — escolta.
2. Línies d'inici i aturada:
iotv.on_din_change(ADDR, on_din_change)iotv.off_din_change(ADDR) (al bloc finally)3. can_on() i can_off():
Inicialitzen i alliberen el driver del bus CAN físic (TWAI a l'ESP32 o socket CAN a Linux). Al simulador no cal perquè la connexió al bus virtual via MQTT es gestiona automàticament en obrir la pàgina.
4. Per quin motiu no es pot cridar time.sleep() dins del callback:
El callback s'executa en el thread del dispatcher (core 0), mentre el bucle principal corre al core 1. Una funció bloquejant com sleep paralitzaria el dispatcher i podria perdre missatges CAN. El patró correcte és que el callback es limiti a actualitzar una variable volatile, i el bucle principal gestioni l'acció.
Taula comparativa completa:
| Característica | Simulador (polling) | RPi real (callback) |
|---|---|---|
| Latència de resposta | depèn del time.sleep() |
pràcticament immediata |
| Consum de CPU | alt (consulta contínua) | baix (activa només en canvi) |
| Consum de bus CAN | pot saturar-lo | no genera tràfic addicional |
| Complexitat del codi | baixa, seqüencial | mitjana, cal entendre threads |
| Ús recomanat | prototips, simulador | producció, sistemes en temps real |
| Senyals de seguretat | ⚠ latència variable | ✅ resposta garantida |