Código Python en el gemelo digital del IoT-Vertebrae

Resumen

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.

Lo que SÍ funciona

Lo que NO funciona


1. Variables y tipos básicos

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, False y None se convierten automáticamente a true, false y null de JavaScript.


2. Salida por consola: 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 :.2f se eliminan en la transpilación. El valor se muestra con todos sus decimales. Si necesitas formatear, usa int() para truncar o multiplica y divide manualmente.


3. Operadores lógicos

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")

4. Condicionales: if / elif / else

El 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)

Patrón alternativo sin 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.


5. Bucles

Bucle 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)

Bucle for con lista

colores = ["rojo", "verde", "azul"]
for color in colores:
    print("Color:", color)

Bucle while

import time

contador = 0
while contador < 10:
    print("Contador:", contador)
    contador = contador + 1
    time.sleep(0.5)

Bucle infinito con while True

import 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 continue

break 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)

6. Funciones con def

El 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")

Funciones con return

def calcular_porcentaje(voltaje):
    porcentaje = (voltaje / 10.0) * 100
    return porcentaje

v = 7.5
p = calcular_porcentaje(v)
print(f"Voltaje: {v}V = {p}%")

⚠️ Limitaciones de funciones


7. La API iotv — Control del gemelo digital

La 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.

7.1 Operaciones digitales

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)

7.2 Modo PWM

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%

7.3 Operaciones analógicas

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

7.4 Resumen de rangos

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

8. Temporización: 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")

9. Comunicación MQTT

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.


10. Peticiones HTTP: requests

El 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)

11. Funciones matemáticas

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))

12. Conversiones de tipo

# 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"

13. Soluciones a estructuras no soportadas

13.1 Simular append() — Añadir a una lista

El 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]

13.2 Simular len() — Longitud de una lista o string

len() 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

13.3 Simular try / except — Manejo de errores

No 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.

13.4 Simular else alternativo — Evitar lógica compleja

Cuando 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()

13.5 Simular in — Comprobar pertenencia

El 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")

13.6 Simular desempaquetado de variables

# ❌ NO FUNCIONA
a, b = 10, 20

# ✅ SOLUCIÓN: Asignar una por una
a = 10
b = 20

13.7 Simular comprensiones de lista

# ❌ 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)

13.8 Simular 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

13.9 Simular range() descendente en un for

El 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)

14. Ejemplo completo: Semáforo con sensor

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")

15. Ejemplo completo: Onda sinusoidal analógica

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")

16. Ejemplo completo: Publicar por MQTT con LEDs indicadores

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")

Cheatsheet — Referencia rápida

Tipos y conversiones

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')

Operadores

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()

Estructuras de control

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

API 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

Soluciones rápidas a lo NO soportado

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