El simulador IoT-Vertebrae permite escribir código en sintaxis Python que se ejecuta directamente en el navegador. Esto es posible gracias a un transpilador interno que convierte el código Python a JavaScript mediante transformaciones basadas en expresiones regulares (regex).
Esto implica que no se ejecuta Python real, sino una versión transpilada. El transpilador soporta un subconjunto muy útil del lenguaje — suficiente para programar IoT de forma educativa — pero tiene limitaciones importantes que hay que conocer para evitar errores.
print() con múltiples argumentos y f-stringsif, elif, elsefor ... in range(...), for ... in lista, whiledef (sin valores por defecto en parámetros)True, False, None → convertidos a true, false, nulland, or, not → convertidos a &&, ||, ![]), diccionarios ({}), acceso por índicetime.sleep() (convertido a await automáticamente)range() con 1, 2 o 3 argumentosf"texto {variable}" y f"valor: {x:.2f}"int(), float(), str(), min(), max(), abs()math: math.sin(), math.cos(), math.sqrt(), math.pi, etc.bin(), .zfill(), slicing [n:].strip() → convertido a .trim()pass → convertido a /* pass */import, from) → se ignoran (comentadas)iotv.* para controlar vértebras digitales y analógicaspaho.mqtt.clientrequests.get() y requests.post()serial.Serial()# → convertidos a //else en la misma línea que un if en una sola líneatry / except / finallyclass)[x for x in ...]lambdaa, b = 1, 2:=in / not in para comprobar pertenenciais / is not**kwargs, *args, valores por defecto en parámetros@)yield (excepto el range interno)with (context managers)set[a:b:c] (solo soporta [n:]).append(), .pop(), .insert(), .remove().split(), .join(), .replace(), .upper(), .lower(), .startswith(), .endswith() (estos SÍ funcionan porque JavaScript tiene equivalentes nativos, excepto .split() y .join() que también funcionan directamente)len() (no existe en el contexto de ejecución).keys(), .values(), .items()return fuera de una función (como sentencia de salida del programa)Las variables se declaran por asignación directa, igual que en Python estándar. El transpilador no añade let ni var, de modo que las variables son globales en el ámbito de la función ejecutora.
# Variables numéricas
temperatura = 25.5
umbral = 30
contador = 0
# Strings
nombre = "Sensor-01"
estado = 'activo'
# Booleanos
activo = True
alarma = False
# None
resultado = None
# Listas
valores = [10, 20, 30, 40]
nombres = ["A", "B", "C"]
# Diccionarios
config = {"puerto": 8084, "host": "broker.emqx.io"}
Nota:
True,FalseyNonese convierten automáticamente atrue,falseynullde JavaScript.
print()La función print() muestra texto en la consola integrada del simulador.
# Print básico
print("Hola, IoT-Vertebrae!")
# Múltiples argumentos (separados por espacio)
valor = 42
print("El valor es:", valor)
# F-strings
sensor = "BME280"
temp = 23.7
print(f"Sensor {sensor}: {temp} °C")
# F-strings con formato (los especificadores se ignoran)
pi = 3.14159
print(f"Pi = {pi:.2f}")
# Concatenación con +
nombre = "Placa"
print("Dispositivo: " + nombre + " activo")
Limitación: los especificadores de formato como
:.2fse eliminan en la transpilación. El valor se muestra con todos sus decimales. Si necesitas formatear, usaint()para truncar o multiplica y divide manualmente.
Los operadores Python se convierten automáticamente:
# and → &&
if temperatura > 20 and temperatura < 30:
print("Rango normal")
# or → ||
if alarma or emergencia:
print("Alerta!")
# not → !
if not activo:
print("Sistema apagado")
if / elif / elseEl transpilador soporta condicionales completos con if, elif y else.
valor = iotv.ain("0x0", "b", 1)
voltaje = iotv.ain2v(valor)
if voltaje > 8.0:
print("Nivel ALTO")
iotv.doutbit("0x0", "a", 0, 1)
elif voltaje > 3.0:
print("Nivel MEDIO")
iotv.doutbit("0x0", "a", 1, 1)
else:
print("Nivel BAJO")
iotv.dout("0x0", "a", 0)
else (recomendado para lógica simple)El transpilador funciona de forma más predecible cuando se usan condiciones independientes en lugar de if/else:
# Alternativa a if/else usando condiciones opuestas
direction = 1
if direction == 1:
print("Adelante")
if direction != 1:
print("Atrás")
Este patrón es el más usado en los ejemplos del sistema porque evita posibles problemas de indentación con bloques else complejos.
for con range()# range(n) → de 0 a n-1
for i in range(5):
print("Iteración:", i)
# range(inicio, fin)
for i in range(1, 11):
print("Número:", i)
# range(inicio, fin, paso) — incluyendo negativos
for i in range(10, 0, -1):
print("Cuenta atrás:", i)
for con listacolores = ["rojo", "verde", "azul"]
for color in colores:
print("Color:", color)
whileimport time
contador = 0
while contador < 10:
print("Contador:", contador)
contador = contador + 1
time.sleep(0.5)
while Trueimport time
while True:
valor = iotv.dinbit("0x0", "b", 0)
print("Lectura:", valor)
time.sleep(1)
Para detener un bucle infinito, pulsa el botón STOP del editor.
break y continuebreak y continue se pasan directamente a JavaScript y funcionan correctamente:
while True:
sensor = iotv.dinbit("0x0", "b", 7)
if sensor == 1:
print("Sensor activado, saliendo")
break
time.sleep(0.1)
defEl transpilador convierte funciones def en funciones JavaScript. Si la función contiene time.sleep(), llamadas a requests, operaciones serie, o llamadas a otras funciones async, se marca automáticamente como async.
import iotv
import time
def encender_led(vertebra, costilla, bit):
iotv.doutbit(vertebra, costilla, bit, 1)
print("LED encendido")
def apagar_led(vertebra, costilla, bit):
iotv.doutbit(vertebra, costilla, bit, 0)
print("LED apagado")
def parpadear(vertebra, costilla, bit, veces):
for i in range(veces):
encender_led(vertebra, costilla, bit)
time.sleep(0.5)
apagar_led(vertebra, costilla, bit)
time.sleep(0.5)
# Usar las funciones
parpadear("0x0", "a", 0, 5)
print("Fin del parpadeo")
returndef calcular_porcentaje(voltaje):
porcentaje = (voltaje / 10.0) * 100
return porcentaje
v = 7.5
p = calcular_porcentaje(v)
print(f"Voltaje: {v}V = {p}%")
def func(x=10) NO funciona.*args ni **kwargs.return como sentencia de salida del programa principal no funciona como se espera (en Python real, return fuera de una función no es válido, pero se usa en algunos ejemplos del sistema para salir del programa; funciona porque el código se envuelve en una función async).iotv — Control del gemelo digitalLa API iotv es el puente entre tu código Python y el hardware virtual. No necesita import real (se ignora), pero es buena práctica incluirlo para compatibilidad.
import iotv
# Escribir byte completo (8 bits) a una costilla
iotv.dout("0x0", "a", 0xFF) # Todos los bits a 1
iotv.dout("0x0", "a", 0x00) # Todos los bits a 0
iotv.dout("0x0", "a", 0b10101010) # Patrón alternado
# Escribir un bit individual
iotv.doutbit("0x0", "a", 0, 1) # Bit 0 a ON
iotv.doutbit("0x0", "a", 0, 0) # Bit 0 a OFF
# Leer byte de entrada (devuelve string binario "00000000")
entrada = iotv.din("0x0", "b")
print("Entrada:", entrada)
# Leer bit individual (devuelve 0 o 1)
boton = iotv.dinbit("0x0", "b", 7)
print("Botón:", boton)
import iotv
# Configurar costilla A como salida PWM
iotv.dsetup("0x0", "aoutpwm", "bin")
# Escribir valor PWM (0-255) al byte completo
iotv.doutpwm("0x0", "a", 128) # 50% de duty cycle
# PWM en bit individual
iotv.doutbitpwm("0x0", "a", 0, 200) # Bit 0 al 78%
import iotv
# Leer entrada analógica (valor digital 0-4095)
valor = iotv.ain("0x0", "b", 1) # Canal 1 de costilla B
print("Valor ADC:", valor)
# Convertir a voltaje (-10V a +10V)
voltaje = iotv.ain2v(valor)
print("Voltaje:", voltaje, "V")
# Escribir salida analógica (valor digital 0-4095)
iotv.aout("0x0", "a", 1, 2048) # Canal 1, valor medio
# Convertir voltaje a valor DAC
valor_dac = iotv.v2aout(7.5) # 7.5V → valor digital
iotv.aout("0x0", "a", 1, valor_dac)
# Conversiones de utilidad
voltaje_adc = iotv.ain2v(3000) # ADC → voltaje
voltaje_dac = iotv.aout2v(2048) # DAC → voltaje
valor_adc = iotv.v2ain(5.0) # Voltaje → ADC
valor_dac = iotv.v2aout(5.0) # Voltaje → DAC
| Función | Rango entrada | Rango salida |
|---|---|---|
ain() |
Canal 1-4 | 0 – 4095 (digital) |
ain2v() |
0 – 4095 | -10V a +10V |
v2ain() |
-10V a +10V | 0 – 4095 |
aout() |
Canal 1-4, valor 0-4095 | — |
aout2v() |
0 – 4095 | 0V a 10V |
v2aout() |
0V a 10V | 0 – 4095 |
time.sleep()time.sleep(segundos) se convierte automáticamente en await time.sleep(segundos). Esto permite pausas no bloqueantes en el navegador.
import time
print("Inicio")
time.sleep(1) # Pausa de 1 segundo
print("Después de 1s")
time.sleep(0.5) # Pausa de 500ms
print("Después de 0.5s")
También se puede usar time.time() para obtener el timestamp actual (en segundos):
import time
inicio = time.time()
time.sleep(2)
fin = time.time()
duracion = fin - inicio
print("Duración:", duracion, "s")
El sistema incluye un cliente MQTT compatible con la API de Paho Python, que funciona por WebSocket.
import paho.mqtt.client as mqtt
import time
# Crear cliente
client = mqtt.Client("mi-cliente-001")
# Callback de conexión
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Conectado al broker MQTT")
client.subscribe("iot-vertebrae/comandos")
else:
print("Error de conexión")
# Callback de mensaje
def on_message(client, userdata, msg):
print("Mensaje:", msg.payload)
if msg.payload == "ON":
iotv.dout("0x0", "a", 0xFF)
if msg.payload == "OFF":
iotv.dout("0x0", "a", 0x00)
# Asignar callbacks
client.on_connect = on_connect
client.on_message = on_message
# Conectar (host, puerto WSS, keepalive)
client.connect("broker.emqx.io", 8084, 60)
client.loop_start()
# Publicar datos periódicamente
for i in range(30):
switches = iotv.din("0x0", "b")
client.publish("iot-vertebrae/estado", switches)
print("Publicado:", switches)
time.sleep(2)
# Desconectar
client.loop_stop()
client.disconnect()
print("Desconectado")
Nota: el puerto 8084 usa WSS (WebSocket Seguro). El puerto 8083 usaría WS sin cifrar.
requestsEl módulo requests está emulado con fetch() del navegador. Las llamadas se convierten automáticamente a await.
import requests
# GET
response = requests.get("https://api.example.com/datos")
print("Status:", response.status_code)
print("Texto:", response.text)
# Parsear JSON
datos = response.json()
print("JSON:", datos)
# POST
response = requests.post("https://api.example.com/enviar", {"clave": "valor"})
print("Respuesta:", response.text)
import math
# Constantes
print("Pi:", math.pi)
print("e:", math.e)
# Funciones trigonométricas
angulo = math.pi / 4
print("sin:", math.sin(angulo))
print("cos:", math.cos(angulo))
print("tan:", math.tan(angulo))
# Otras funciones
print("sqrt(16):", math.sqrt(16))
print("floor(3.7):", math.floor(3.7))
print("ceil(3.2):", math.ceil(3.2))
# Funciones globales
print("abs(-5):", abs(-5))
print("min(3,7):", min(3, 7))
print("max(3,7):", max(3, 7))
# String a entero
texto = "42"
numero = int(texto)
print(numero + 8) # → 50
# String a decimal
texto = "3.14"
decimal = float(texto)
# Número a string
valor = 255
texto = str(valor)
print("Valor: " + texto)
# Número a binario (devuelve string como "0b11111111")
binario = bin(255)
print(binario)
# Rellenar con ceros
texto = str(5).zfill(3) # → "005"
append() — Añadir a una listaEl método .append() de Python no existe en el entorno transpilado. La alternativa es usar concatenación con + para crear una nueva lista, o usar el método nativo de JavaScript .push() que sí funciona en el entorno transpilado.
# ❌ NO FUNCIONA
datos = []
datos.append(42)
# ✅ SOLUCIÓN 1: Concatenar con + (crea nueva lista)
datos = []
datos = datos + [42]
datos = datos + [55]
datos = datos + [78]
print(datos) # → [42, 55, 78]
# ✅ SOLUCIÓN 2: Usar .push() de JavaScript (funciona en el transpilador)
datos = []
datos.push(42)
datos.push(55)
datos.push(78)
print(datos) # → [42, 55, 78]
len() — Longitud de una lista o stringlen() no está en el contexto de ejecución. Se puede usar la propiedad .length de JavaScript:
# ❌ NO FUNCIONA
lista = [1, 2, 3, 4, 5]
n = len(lista)
# ✅ SOLUCIÓN: usar .length
lista = [1, 2, 3, 4, 5]
n = lista.length
print("Longitud:", n) # → 5
texto = "Hola"
n = texto.length
print("Caracteres:", n) # → 4
try / except — Manejo de erroresNo hay soporte para try/except. La estrategia es prevenir errores con comprobaciones previas.
# ❌ NO FUNCIONA
try:
valor = int(texto)
except:
valor = 0
# ✅ SOLUCIÓN: Validar antes de operar
texto = "abc"
valor = 0
resultado = int(texto)
# Si texto no es numérico, int() devolverá NaN
# Pero podemos hacer comprobaciones previas...
# Para caso numérico sencillo:
texto = "42"
valor = int(texto)
print(valor)
Consejo: diseña el flujo para evitar errores en lugar de capturarlos. Valida las entradas antes de operar.
else alternativo — Evitar lógica complejaCuando if/else se complica con múltiples niveles, usa variables de estado:
# ❌ PUEDE FALLAR con anidamiento complejo
if condicion_a:
if condicion_b:
accion1()
else:
accion2()
else:
accion3()
# ✅ SOLUCIÓN: Variables de estado y condiciones independientes
resultado = 0
if condicion_a and condicion_b:
resultado = 1
if condicion_a and not condicion_b:
resultado = 2
if not condicion_a:
resultado = 3
if resultado == 1:
accion1()
if resultado == 2:
accion2()
if resultado == 3:
accion3()
in — Comprobar pertenenciaEl operador in para comprobar si un elemento está en una lista no se transpila correctamente.
# ❌ NO FUNCIONA como se espera
if valor in [1, 2, 3]:
print("Encontrado")
# ✅ SOLUCIÓN 1: Comparaciones individuales
encontrado = 0
if valor == 1:
encontrado = 1
if valor == 2:
encontrado = 1
if valor == 3:
encontrado = 1
if encontrado == 1:
print("Encontrado")
# ✅ SOLUCIÓN 2: Usar .indexOf() de JavaScript (> -1 si existe)
lista = [1, 2, 3]
if lista.indexOf(valor) > -1:
print("Encontrado")
# ❌ NO FUNCIONA
a, b = 10, 20
# ✅ SOLUCIÓN: Asignar una por una
a = 10
b = 20
# ❌ NO FUNCIONA
cuadrados = [x * x for x in range(10)]
# ✅ SOLUCIÓN: Bucle normal
cuadrados = []
for x in range(10):
cuadrados = cuadrados + [x * x]
print(cuadrados)
pop() y acceso al último elemento# ❌ NO FUNCIONA
ultimo = lista.pop()
# ✅ SOLUCIÓN: Usar .pop() de JavaScript (también funciona en el transpilador)
lista = [10, 20, 30]
ultimo = lista.pop()
print(ultimo) # → 30
print(lista) # → [10, 20]
# Para acceder al último sin eliminarlo:
lista = [10, 20, 30]
ultimo = lista[lista.length - 1]
print(ultimo) # → 30
range() descendente en un forEl transpilador convierte for i in range(...) a for (const i of range(...)) y la función range generadora soporta 3 argumentos:
# ✅ FUNCIONA: range con paso negativo
for i in range(7, -1, -1):
print(i) # 7, 6, 5, 4, 3, 2, 1, 0
# ✅ FUNCIONA: lista explícita como alternativa
valores_desc = [255, 200, 150, 100, 50, 0]
for v in valores_desc:
iotv.doutpwm("0x0", "a", v)
time.sleep(0.1)
import iotv
import time
# Configuración
vertebra = "0x0"
costilla = "a"
# Bits para cada color
ROJO = 0
AMARILLO = 1
VERDE = 2
def apagar_todos():
iotv.doutbit(vertebra, costilla, ROJO, 0)
iotv.doutbit(vertebra, costilla, AMARILLO, 0)
iotv.doutbit(vertebra, costilla, VERDE, 0)
print("=== SEMÁFORO ===")
print("Rojo=bit0, Amarillo=bit1, Verde=bit2")
for ciclo in range(3):
print(f"Ciclo {ciclo + 1}/3")
# Verde (2 segundos)
apagar_todos()
iotv.doutbit(vertebra, costilla, VERDE, 1)
print(" VERDE - Pasar")
time.sleep(2)
# Amarillo (1 segundo)
apagar_todos()
iotv.doutbit(vertebra, costilla, AMARILLO, 1)
print(" AMARILLO - Precaución")
time.sleep(1)
# Rojo (2 segundos)
apagar_todos()
iotv.doutbit(vertebra, costilla, ROJO, 1)
print(" ROJO - Parar")
time.sleep(2)
apagar_todos()
print("Semáforo finalizado")
import iotv
import time
import math
vertebra = "0x0"
costilla = "a"
canal = 1
print("Generando onda sinusoidal")
print("Amplitud: 5V, Offset: 5V")
# Generar 2 ciclos completos
for i in range(40):
# Calcular ángulo (0 a 2*pi por ciclo)
angulo = (i / 20) * 2 * math.pi
# Voltaje: sin(angulo)*5 + 5 → oscila entre 0V y 10V
voltaje = math.sin(angulo) * 5 + 5
# Convertir y escribir
valor = iotv.v2aout(voltaje)
iotv.aout(vertebra, costilla, canal, valor)
print(f"Angulo: {angulo} rad | Voltaje: {voltaje} V")
time.sleep(0.1)
# Reset
iotv.aout(vertebra, costilla, canal, 0)
print("Onda finalizada")
import paho.mqtt.client as mqtt
import time
print("=== Telemetría MQTT ===")
client = mqtt.Client("sensor-iotv-001")
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Conectado al broker")
else:
print("Error de conexión, código: " + str(rc))
client.on_connect = on_connect
client.connect("broker.emqx.io", 8084, 60)
client.loop_start()
for i in range(30):
# Leer sensor analógico
adc = iotv.ain("0x0", "b", 1)
voltaje = iotv.ain2v(adc)
# Simular temperatura: -10V..+10V → 0°C..100°C
temperatura = (voltaje + 10) * 5
# Publicar
client.publish("iot-vertebrae/sensor/voltaje", str(voltaje))
client.publish("iot-vertebrae/sensor/temperatura", str(temperatura))
# Indicador LED: nivel proporcional
nivel = int((voltaje + 10) / 20 * 8)
leds = 0
if nivel > 0:
leds = (1 << nivel) - 1
iotv.dout("0x0", "a", leds)
lectura = i + 1
print("Lectura " + str(lectura) + ": " + str(voltaje) + "V / " + str(temperatura) + "°C")
time.sleep(1)
client.loop_stop()
client.disconnect()
iotv.dout("0x0", "a", 0)
print("Desconectado")
| Python | Transpilado | Notas |
|---|---|---|
True / False |
true / false |
Automático |
None |
null |
Automático |
int("42") |
parseInt("42") |
✅ |
float("3.14") |
parseFloat("3.14") |
✅ |
str(42) |
String(42) |
✅ |
bin(255) |
(255).toString(2) |
✅ |
"5".zfill(3) |
"5".padStart(3, '0') |
✅ |
| Python | JavaScript | Automático |
|---|---|---|
and |
&& |
✅ |
or |
|| |
✅ |
not x |
!x |
✅ |
==, !=, <, > |
igual | ✅ |
x ** 2 |
x ** 2 |
✅ (JS soporta **) |
// (div entera) |
— | ❌ Usar int(a / b) |
% (módulo) |
% |
✅ |
in |
— | ❌ Usar .indexOf() |
| Python | Transpilado | Estado |
|---|---|---|
if ... : |
if (...) { |
✅ |
elif ... : |
} else if (...) { |
✅ |
else: |
} else { |
✅ |
for x in range(n): |
for (const x of range(n)) { |
✅ |
for x in lista: |
for (const x of lista) { |
✅ |
while cond: |
while (cond) { |
✅ |
break |
break |
✅ |
continue |
continue |
✅ |
pass |
/* pass */ |
✅ |
def func(): |
function func() { |
✅ |
return valor |
return valor |
✅ |
try/except |
— | ❌ |
class |
— | ❌ |
lambda |
— | ❌ |
with |
— | ❌ |
iotv — Funciones principales| Función | Descripción |
|---|---|
iotv.dout(addr, side, valor) |
Escribir byte digital (0-255) |
iotv.doutbit(addr, side, bit, val) |
Escribir bit digital (0 o 1) |
iotv.din(addr, side) |
Leer byte digital → string "00000000" |
iotv.dinbit(addr, side, bit) |
Leer bit digital → 0 o 1 |
iotv.doutpwm(addr, side, valor) |
PWM byte (0-255) |
iotv.doutbitpwm(addr, side, bit, val) |
PWM bit individual |
iotv.dsetup(addr, sideA, sideB) |
Configurar modos: aout/ain/aoutpwm/boutpwm/bin/bout |
iotv.ain(addr, side, canal) |
Leer ADC canal 1-4 → 0-4095 |
iotv.aout(addr, side, canal, valor) |
Escribir DAC canal 1-4, valor 0-4095 |
iotv.ain2v(digital) |
ADC a voltaje (-10V a +10V) |
iotv.v2ain(voltaje) |
Voltaje a ADC |
iotv.aout2v(digital) |
DAC a voltaje (0V a 10V) |
iotv.v2aout(voltaje) |
Voltaje a DAC |
| Quiero hacer... | Solución transpilable |
|---|---|
lista.append(x) |
lista = lista + [x] o lista.push(x) |
len(lista) |
lista.length |
a, b = 1, 2 |
a = 1 (nueva línea) b = 2 |
[x*x for x in range(5)] |
Bucle for + concatenación |
x in [1,2,3] |
[1,2,3].indexOf(x) > -1 |
try: ... except: ... |
Validar antes de operar |
lista.pop() |
lista.pop() (JS nativo funciona) |
a // b (división entera) |
int(a / b) |
string.split(",") |
string.split(",") (JS nativo funciona) |
",".join(lista) |
lista.join(",") (en JS el orden es al revés) |
string.upper() |
string.toUpperCase() (método JS) |
string.lower() |
string.toLowerCase() (método JS) |
string.replace("a","b") |
string.replace("a","b") (JS nativo, solo primera ocurrencia) |
abs(-5) |
abs(-5) → transpilado a Math.abs(-5) ✅ |
min(a, b) |
min(a, b) → transpilado a Math.min(a, b) ✅ |
max(a, b) |
max(a, b) → transpilado a Math.max(a, b) ✅ |
print(f"{x:.2f}") |
El formato se ignora; se muestra el valor completo |