Mostrando entradas con la etiqueta programación. Mostrar todas las entradas
Mostrando entradas con la etiqueta programación. Mostrar todas las entradas

jueves, 15 de marzo de 2018

Programación de Enteros Mixtos: Una Guía para la Toma de Decisiones Computacionales

La investigación operativa, la ciencia del uso de computadoras para tomar decisiones óptimas, se ha utilizado y aplicado en muchas áreas del software. Para dar algunos ejemplos, las compañías de logística lo usan para determinar la ruta óptima para llegar del punto A al punto B, las compañías eléctricas lo usan para determinar los programas de producción de energía y las compañías manufactureras lo usan para encontrar patrones de personal óptimos para sus fábricas.
Programación de Enteros Mixtos
Hoy exploraremos el poder de la investigación operativa pasando sobre un problema hipotético. Específicamente, utilizaremos la programación de enteros mixtos (MIP) para determinar el patrón de dotación de personal óptimo para una fábrica hipotética.

Algoritmos de Investigación de Operaciones

Antes de sumergirnos en nuestro problema de ejemplo, es útil repasar algunos conceptos básicos en la investigación de operaciones y comprender algunas de las herramientas que usaremos hoy.

Algoritmos de Programación Lineal

La programación lineal es una técnica de investigación de operaciones utilizada para determinar el mejor resultado en un modelo matemático donde el objetivo y las restricciones se expresan como un sistema de ecuaciones lineales. Un ejemplo de modelo de programación lineal podría verse así:
Maximize a + b (objetive)
Subject a:
a <= 2 (restriction 1)
b <= 3 (restriction 2)
En nuestro ejemplo simple, podemos ver que el resultado óptimo es 5, con a = 2 y b = 3. Si bien este es un ejemplo bastante trivial, es probable que puedas imaginar un modelo de programación lineal que utiliza miles de variables y cientos de restricciones.
Un buen ejemplo podría ser un problema de portafolio de inversión en el que podrías terminar con algo como lo siguiente, en un pseudo-código:
Maximize <expected profit from all stock investments>

Subject to:
<investment in the technology sector must be between 10% - 20% of portfolio>
<investment in the consumer staples must not exceed investment in financials>
Etc.
...
Lo cual sería bastante complejo y difícil de resolver a mano o por prueba y error. El software de investigación operativa utilizará varios algoritmos para resolver estos problemas rápidamente. Si estás interesado en los algoritmos subyacentes, te recomiendo que aprendas sobre el método símplex aquí y el método del punto interior aquí.

Algoritmos de Programación Enteros

La programación entera es como la programación lineal con una tolerancia adicional para que algunas o todas las variables sean valores enteros. Si bien esto puede no parecer una gran mejora al principio, nos permite resolver muchos problemas que podrían haberse quedado sin resolver utilizando únicamente la programación lineal.
Uno de estos problemas es el problema de la mochila, en el que se nos da un conjunto de elementos con valores y pesos asignados y se les pide que encuentren la combinación de los elementos que más cabe en nuestra mochila. Un modelo de programación lineal no podrá resolver esto, debido a que no hay forma de expresar la idea de que puedes poner un artículo en su mochila o no, pero no puedes poner parte de un artículo en tu mochila—¡cada variable es una variable continua!
Un ejemplo de problema de mochila podría tener los siguientes parámetros:
ObjectValue (units of $10)Size (generic units)
Camera52
Mysterious figurine74
Huge bottle of apple cider27
French horn1010
Y el modelo MIP se verá así:
Maximize 5a + 7b + 2c + 10d (objective: maximize value of items take)
Subject to:
    2a + 4b + 7c + 10d <= 15 (space constraint)
La solución óptima, en este caso, es a=0, b=1, c=0, d=1, con el valor del ítem total siendo 17.
El problema que resolveremos hoy también requerirá una programación entera ya que un empleado en una fábrica puede programarse para un turno o no—en aras de la simplicidad, no puedes programar un empleado para 2/3 de turno en esta fábrica.
Para hacer posible la programación de enteros se usan varios algoritmos matemáticos. Si estás interesado en los algoritmos subyacentes, te recomiendo que estudies el algoritmo de planos de corte y el algoritmo de ramificación y enlace aquí.

Problema Ejemplo: Programación

Descripción del Problema

Hoy exploraremos el problema de dotar de personal a una fábrica. Como la gerencia de la fábrica, queremos minimizar los costos de mano de obra pero queremos asegurar una cobertura suficiente para cada turno para así satisfacer la demanda de producción.
Supongamos que tenemos cinco turnos con las siguientes demandas de personal:
Shift 1Shift 2Shift 3Shift 4Shift 5
1 person4 people3 people5 people2 people
Y supongamos que tenemos los siguientes trabajadores:
NameAvailabilityCost per Shift ($)
Melisandre1, 2, 520
Bran2, 3, 4, 515
Cersei3, 435
Daenerys4, 535
Theon2, 4, 510
Jon1, 3, 525
Tyrion2, 4, 530
Jaime2, 3, 520
Arya1, 2, 420
Para mantener el problema simple, supongamos por el momento que la disponibilidad y el costo son las únicas preocupaciones.

Nuestras Herramientas

Para el problema de hoy, usaremos un software de corte y ramificación de código abierto llamado CBC. Interactuaremos con este software usando PuLP, que es una biblioteca de modelado de investigación de operaciones popular para Python. Puedes encontrar información al respecto aquí.
PuLP nos permite construir modelos MIP de una manera muy Pythonica y se integra muy bien con el código Python existente. Esto es muy útil ya que algunas de las herramientas de análisis y manipulación de datos más populares están escritas en Python, y la mayoría de los sistemas de investigación de operaciones comerciales requieren un extenso procesamiento de datos antes y después de la optimización.
Para demostrar la simplicidad y elegancia de PuLP, aquí está el problema de la mochila de antes y resuelto en PuLP:
import pulp as pl

# declarar algunas variables
# cada variable es una variable binaria que es 0 o 1
# 1 significa que el artículo irá a la mochila
a = pl.LpVariable("a", 0, 1, pl.LpInteger)
b = pl.LpVariable("b", 0, 1, pl.LpInteger)
c = pl.LpVariable("c", 0, 1, pl.LpInteger)
d = pl.LpVariable("d", 0, 1, pl.LpInteger)

# define el problema
prob = pl.LpProblem("knapsack", pl.LpMaximize)

# función objetivo - maximizar el valor de los objetos en la mochila
prob += 5 * a + 7 * b + 2 * c + 10 * d

# restricción: el peso de los objetos no puede exceder 15
prob += 2 * a + 4 * b + 7 * c + 10 * d <= 15

estado = prob.solve()  # resuelve usando el solucionador predeterminado, que es cbc
print(pl.LpStatus[status])  # imprime el estado legible por humanos

# imprime los valores
print("a", pl.value(a))
print("b", pl.value(b))
print("c", pl.value(c))
print("d", pl.value(d))
Ejecuta esto, y deberías obtener la salida:
Optimal
a 0.0
b 1.0
c 0.0
d 1.0
¡Ahora a nuestro problema de programación!

Codificando Nuestra Solución

La codificación de nuestra solución es bastante sencilla. Primero, definiremos nuestros datos:
import pulp as pl
import collections as cl

# data
shift_requirements = [1, 4, 3, 5, 2]
workers = {
    "Melisandre": {
        "availability": [0, 1, 4],
        "cost": 20
    },
    "Bran": {
        "availability": [1, 2, 3, 4],
        "cost": 15
    },
    "Cersei": {
        "availability": [2, 3],
        "cost": 35
    },
    "Daenerys": {
        "availability": [3, 4],
        "cost": 35
    },
    "Theon": {
        "availability": [1, 3, 4],
        "cost": 10
    },
    "Jon": {
        "availability": [0, 2, 4],
        "cost": 25
    },
    "Tyrion": {
        "availability": [1, 3, 4],
        "cost": 30
    },
    "Jaime": {
        "availability": [1, 2, 4],
        "cost": 20
    },
    "Arya": {
        "availability": [0, 1, 3],
        "cost": 20
    }
}
A continuación, debemos definir el modelo:
# define el modelo: queremos minimizar el costo
prob = pl.LpProblem("scheduling", pl.LpMinimize)

# algunos modelos de variable
cost = []
vars_by_shift = cl.defaultdict(list)

for worker, info in workers.items():
    for shift in info['availability']:
        worker_var = pl.LpVariable("%s_%s" % (worker, shift), 0, 1, pl.LpInteger)
        vars_by_shift[shift].append(worker_var)
        cost.append(worker_var * info['cost'])

# establece el objetivo para que sea la suma del costo
prob += sum(cost)

# establece los requerimientos de cambio
for shift, requirement in enumerate(shift_requirements):
    prob += sum(vars_by_shift[shift]) >= requirement
Y ahora, ¡le pedimos que resuelva e imprima los resultados!
status = prob.solve()
print("Result:", pl.LpStatus[status])
results = []
for shift, vars in vars_by_shift.items():
    results.append({
        "shift": shift,
        "workers": [var.name for var in vars if var.varValue == 1],
    })

for result in sorted(results, key=lambda x: x['shift']):
    print("Shift:", result['shift'], 'workers:', ', '.join(result['workers']))
Una vez que ejecutas el código, deberías ver las siguientes salidas:
Result: Optimal
Shift: 0 workers: Arya_0
Shift: 1 workers: Melisandre_1, Bran_1, Theon_1, Arya_1
Shift: 2 workers: Bran_2, Jon_2, Jaime_2
Shift: 3 workers: Bran_3, Daenerys_3, Theon_3, Tyrion_3, Arya_3
Shift: 4 workers: Bran_4, Theon_4

Aumenta un Poco la Dificultad: Restricciones Adicionales

Aunque el modelo anterior fue interesante y útil, no demuestra completamente el poder de la programación de enteros mixtos. También podríamos escribir fácilmente un ciclo for para encontrar los trabajadores x más baratos para cada turno, donde x es la cantidad de trabajadores necesarios para un turno. Para demostrar cómo MIP se puede usar para resolver un problema que sería difícil de resolver de manera imperativa, examinemos qué pasaría si agregamos algunas restricciones adicionales.

Restricciones Adicionales

Supongamos que debido a las nuevas regulaciones laborales, no se pueden asignar trabajadores a más de 2 turnos. Para dar cuenta del aumento del trabajo, hemos contratado la ayuda de Dothraki Staffing Group, que proporcionará hasta 10 trabajadores Dothraki por cada turno a una tasa de 40 dólares por turno.
Además supongamos que, debido a algunos conflictos interpersonales en curso fuera de nuestra fábrica, Cersei y Jaime no pueden trabajar en turnos ni con Daenerys ni con Jon. Además, Jaime y Cersei no pueden trabajar ningún turno juntos. Finalmente, Arya, que tiene relaciones interpersonales particularmente complejas, no puede trabajar en el mismo turno con Jaime, Cersei o Melisandre.
La adición de estas dos nuevas limitaciones y recursos de ninguna manera hace que el problema sea imposible de resolver a través de medios imperativos, pero hace que la solución sea mucho más difícil ya que uno tendrá que considerar los costos de oportunidad de programar a un trabajador para un turno en particular.

Solución

Con la programación de enteros mixtos, sin embargo, es mucho más fácil. Simplemente necesitamos enmendar nuestro código así:
Definir la lista de prohibiciones y algunas restricciones:
ban_list = {
    ("Daenerys", "Jaime"),
    ("Daenerys", "Cersei"),
    ("Jon", "Jaime"),
    ("Jon", "Cersei"),
    ("Arya", "Jaime"),
    ("Arya", "Cersei"),
    ("Arya", "Melisandre"),
    ("Jaime", "Cersei")
}

DOTHRAKI_MAX = 10
DOTHRAKI_COST = 45
Rellena algunas variables para implementar las restricciones de prohibición y cambio máximo:
for worker, info in workers.items():
    for shift in info['availability']:
        worker_var = pl.LpVariable("%s_%d" % (worker, shift), 0, 1, pl.LpInteger)
        # almacena algunos datos variables para que podamos implementar la restricción de prohibición
        var_data = (worker,)
        vars_by_shift[shift].append((worker_var, var_data))
        # almacena vars por variable para que podamos implementar la restricción de cambio máximo
        vars_by_worker[worker].append(worker_var)
        cost.append(worker_var * info['cost'])
Agrega las variables Dothraki:
for shift in range(len(shift_requirements)):
    dothraki_var = pl.LpVariable('dothraki_%d' % shift, 0, DOTHRAKI_MAX, pl.LpInteger)
    cost.append(dothraki_var * DOTHRAKI_COST)
    dothrakis_by_shift[shift] = dothraki_var
También necesitaremos un bucle ligeramente modificado para calcular los requisitos de cambio y prohibición:
# establece los requerimientos de cambio
for shift, requirement in enumerate(shift_requirements):
    prob += sum([var[0] for var in vars_by_shift[shift]]) + dothrakis_by_shift[shift] >= requirement

# establece los requerimientos de prohibición
for shift, vars in vars_by_shift.items():
    for var1 in vars:
        for var2 in vars:
            worker_pair = var1[1][0], var2[1][0]
            if worker_pair in ban_list:
                prob += var1[0] + var2[0] <= 1

# establece los estándares de trabajo:
for worker, vars in vars_by_worker.items():
    prob += sum(vars) <= 2
Y finalmente, para imprimir los resultados:
status = prob.solve()
print("Result:", pl.LpStatus[status])
results = []
for shift, vars in vars_by_shift.items():
    results.append({
        "shift": shift,
        "workers": [var[1][0] for var in vars if var[0].varValue == 1],
        "dothrakis": dothrakis_by_shift[shift].varValue
    })

for result in sorted(results, key=lambda x: x['shift']):
    print("Shift:", result['shift'], 'workers:', ', '.join(result['workers']), 'dothrakis hired:', int(result['dothrakis']))
Y deberíamos estar listos. Ejecuta el código y deberías ver el resultado de la siguiente manera:
Resultado: Óptimo
Shift: 0 trabajadores: Arya dothrakis contratados: 0
Shift: 1 trabajador: Melisandre, Theon, Tyrion, Jaime dothrakis contratados: 0
Shift: 2 trabajadores: Bran, Jon dothrakis contratados: 1
Shift: 3 trabajadores: Bran, Daenerys, Theon, Tyrion, Arya dothrakis contratados: 0
Shift: 4 trabajadores: Melisandre, Jaime dothrakis contratados: 0
Y listo, un resultado que respeta la lista de trabajadores prohibidos sigue las regulaciones laborales y usa juiciosamente a los trabajadores Dothraki.
Este articulo fue escrito por Shanglun Wang. Originalmente publicado en Toptal.

viernes, 16 de junio de 2017

Programación Visual con Node-Red: Conectando el Internet de las Cosas con Facilidad

A través de la programación, hacemos que las máquinas imiten un comportamiento complejo siguiendo secuencias de instrucciones simples. Utilizar lenguajes de programación textual como Asambly, C, Python y JavaScript, es una de las formas principales de hacerlo. Los diseñadores de estos lenguajes de programación han pasado horas y horas tratando de hacer que la experiencia de los programas de escritura sean lo más fácil posible a través de una sintaxis expresiva, fuertes construcciones de programación y cadenas de herramientas poderosas. Sin embargo, todos estos lenguajes de programación comparten un rasgo común: el código fuente textual.
Escribir programas en texto funciona y en la mayoría de los casos funciona bien. Sin embargo, la capacidad de expresar programas visualmente es a menudo deseable. Ser capaz de diseñar el flujo de información a través de diversos componentes de un sistema más grande es normalmente todo lo que se necesita. Las herramientas visuales de programación también son indulgentes con cualquier persona que sea nuevo en la programación y que les es difícil manejar diversos conceptos como las variables, indicadores, señales, alcances, y así sucesivamente.
Connect Hardware Devices With APIs Using Node-RED
Node-RED es una herramienta de programación visual. Muestra visualmente las relaciones y funciones, y permite al usuario programar sin tener que escribir una lengua. Node-RED es un editor de flujo basado en el navegador donde se puede añadir o eliminar nodos y conectarlos entre sí con el fin de hacer que se comuniquen entre ellos.
En Node-RED, cada nodo es uno de los siguientes dos tipos: un nodo de inyección o un nodo de función. Los nodos de inyección producen un mensaje sin necesidad de entrada y lanzan el mensaje al siguiente nodo conectado a éste. Los nodos de función, por el contrario, tienen una entrada y realizan algún trabajo en él. Con una gran cantidad de estos nodos para elegir, Node-Red hace que el conectar los dispositivos de hardware, APIs y servicios en línea sea más fácil que nunca.

Primeros Pasos con el Node-Red

Node-Red se basa en Node.js. Para instalar el Node-Red, necesitas tener tanto Node.js instalado como NPM. Con NPM, es muy fácil instalar Node-Red: npm install -g node-red
El editor de flujo de Node-Red es una aplicación basada en navegadores web. Para poder utilizarlo, ejecuta Node-Red:
npm install -g node-red
Node-RED’s flow editor is a web browser based application. To be able to use it, run Node-RED:
node-red
&hellip y vaya a http: // localhost: 1880.

¡Hola Mundo!

¿Qué tutorial de programación para principiantes está completo sin aprender a decir “Hola, mundo”? Vamos a empezar por probar exactamente eso:
  1. Arrastra y suelta un nodo de inyección en el editor de flujo. Después, haz doble clic y fija la carga útil como cadena y escribe “Hola mundo”.
  2. Arrastra y suelta un nodo de depuración, de la misma manera como lo hiciste con la inyección.
  3. Conéctalos.
  4. Haz clic en el botón “Deploy” en la esquina derecha.
  5. Haz clic en el botón azul justo a la izquierda del nodo de inyección.
Inténtalo. Verás algo como esto:

Sólo JavaScript

Con Node-Red, no te limites con simples nodos y funcionalidades. Como Node-Red está construido sobre Node.js, todo está impulsado por JavaScript. Los nodos son, de hecho, Node.js módulos. Se pueden encontrar en http://flows.nodered.org/, entonces para añadirlos a tu panel de la izquierda se pueden simplemente “instalar con NPM “. De hecho, puedes desarrollar tu propio flujo y subirlos al repositorio de flujo. Las aplicaciones pueden ser tan complejas como desees ya que puedes escribir JavaScript en los nodos de función dentro del editor de código que proporciona Node-Red.
Ya que la plataforma se basa en Node.js, ésta se aprovecha del mismo modelo orientado a eventos y sin bloqueos. Así que una aplicación construida en Node-Red puede ejecutarse en hardware de bajo costo como el Raspberry Pi, así como en la nube.

Ahora Nos Vamos a Meter en las Grandes Ligas: Es hora de Automatizar el Hogar

Para demostrar cómo Node-Red se ajusta en el ámbito del Internet de las Cosas, vamos a construir una aplicación para cambiar el color de una bombilla inteligente. No todo el mundo puede tener el mismo sistema de iluminación inteligente a su disposición, pero no hay nada de qué preocuparse, ya que se puede encontrar el módulo Node-Red apropiado desde el repositorio oficial de flujo. Sin embargo, para hacer las cosas aún más fácil vamos por algo más inteligente.
Déjmae presentarte a Netbeast. Es una plataforma de código abierto para el desarrollo de aplicaciones para los aparatos y dispositivos del Internet de las cosas sin tener que preocuparse por los detalles como los protocolos inalámbricos, compatibilidad de marca, o tener que saber cómo tratar con cada API específica que existe. ¡Nos permite utilizar los dispositivos virtuales que actúan como los de verdad! Así que incluso si no tienes una bombilla inteligente, tienes una virtual disponible.
Podemos instalar el paquete de NPM de Netbeast para Node-Red a nivel mundial así:
npm install -g node-red-contrib-netbeast
El netbeast-red node representará el panel de control Netbeast, lo que traducirá sus primitivas API para todos los dispositivos inteligentes que tienes en casa. ¡Afortunadamente, también está disponible como un módulo!
Inicia Netbeast:
npm install -g netbeast-cli
netbeast start
Esto hará que el panel esté disponible en el puerto 8000 y SSL en 8443. A continuación, abre el navegador a http://localhost:8000 y navega a Explore. Ahí podemos encontrar muchas aplicaciones y plugins. Busca las marcas de tus bombillas inteligente (Philips Hue, LIFX, Wemo) o si no tienes una, intenta descargar el plugin de la bombilla. ¡Comprueba que los plugins del panel contienen uno de estos!
La insignia amarilla indica que los plugins se están ejecutando, pero no pueden encontrar ningún dispositivo. Haz clic en el plugin de la bombilla para crear una bombilla virtual. Cualquier otro dispositivo que se descubra debe aparecer en la red.
Con todo en su lugar, volvamos al trabajo. Haremos un flujo simple:
  1. Arrastra y suelta un nodo de inyección.
  2. Arrastra y suelta el nodo Netbeast.
  3. Arrastra y suelta un nodo de depuración.
  4. Conecta todo como se muestra a continuación:
Ahora vamos a enviar una petición HTTP al panel de control. Al usar la API Netbeast se tendrá que enviar a través del nodo de inyección un JSON que contiene los valores que queremos que se activen en nuestra bombilla.
¡Presiona el botón para inyectar el color y la energía a todas tus bombillas inteligentes!
Cada tema representa un tipo diferente de dispositivo. Así que hay temas no solo para las luces, sino también para la música, calefacción, y el vídeo; así como sensores para la humedad, la presencia, la temperatura, y la lista continúa. Puedes encontrar en tu documentación una lista de temas y su estructura recomendada para ser traducido a todo tipo de dispositivos. Este motor de la IO es novato, pero potente. Un código abierto que permite a los desarrolladores reutilizar la información para crear escenarios realmente conectados, siendo de este modo inteligente.

Ahora Adentrémonos Más

A continuación, vamos a crear un segundo flujo usando otro plugin, un detector de ruido ambiental para utilizarlo como un iniciador para cambiar el color de la bombilla como un semáforo de ruido. En este tutorial vamos a utilizar uno virtual, así que no hay necesidad de comprar un nuevo hardware. Vamos a empezar haciendo clic en el botón “más” en el editor de Node-Red.
Ve de nuevo al Panel http://localhost:8000/explore a la sección Explorar y busca Volume-Plugin. Se trata de una aplicación web muy rudimentaria que se aprovecha de getUserMedia() dentro del navegador para capturar la información multimedia desde una simple aplicación HTML. Por lo tanto, es probable que sólo funcione en los navegadores modernos.
Haz clic en él para abrir, al igual que con la bombilla virtual. En él se solicita permiso para grabar desde el micrófono. A continuación, enviará los mensajes al intermediario MQTT de Netbeast, que será compartido a través de todo el panel de control, y así podremos suscribirnos. Para lograr esto sólo necesitaremos arrastrar y soltar un nodo de netbeast-trigger en el editor node red. A continuación, vamos a insertar una función entre el trigger y el nodo Netbeast, para decidir cuando está demasiado alto y cuando no. Además, deberíamos utilizar algunos nodos de depuración para comprobar si todo está bien. El esquema ahora se verá algo como esto:
Ahora, vamos a introducir algún código en el nodo de función tooLoud. Sí, sé que prometí que podrías programar sin necesidad de codificar, ¡pero ya he demostrado que puedes! Y se puede tratar de combinar los diferentes elementos disponibles u otros nodos del registro para llevar a cabo lo siguiente.
var volume = msg.payload.volume

node.log(volume)

if (volume < 50) {
    return { topic: 'lights', payload: { power: 1, color: '#00CC00'}}
} else if (volume < 75) {
    return { topic: 'lights', payload: { power: 1, color: '#CCCC00'}}
} else {
    return { topic: 'lights', payload: { power: 1, color: '#FF0000'}}
}
Este fragmento de código bastante simple está regresando una de las tres cargas útiles para el siguiente nodo con un código de color específico, dependiendo del nivel de volumen reportado por el nodo anterior.
¡Ahora estamos listos para continuar! Vamos a presionar el botón Deploy de nuevo y armemos un griterío. ¡Veamos cómo cambia la bombilla de un color a otro de inmediato!
Ya que el micrófono y el navegador web que estás utilizando podrían ser diferentes, no dudes en ajustar los valores de la función y umbrales, y también juega con los valores de color para ver cómo cambia tus bombillas.

Crea tus Propios Plugins

Esta bombilla en CSS puro se inspiró en éste cssdeck.
Como habrás notado, la bombilla virtual anterior es muy rudimentaria, por lo que es posible que desees modificarla. O mejor aún, puedes crear tus propios controladores para un hogar inteligente. Así que vamos a pasar por el proceso de crear un plugin virtual para Netbeast, lo que te permitirá crear tus propios controles para dispositivos inteligentes.
Puedes utilizar el paquete netbeast-cli para generar como por arte de magia algunos códigos. Mediante la ejecución de netbeast create myplugin --plugin tendríamos un proyecto básico como el siguiente:
myplugin
├── README.md
├── index.js
├── package.json
└── test.js

Frontend

Ahora, vamos a empezar a mimetizar la bombilla con un frontend. Los controladores de dispositivos por lo general no van a tener uno, así que el comando de andamio no incluye una carpeta pública todavía. Vamos a crear un directorio public dentro del proyecto y colocamos allí los siguientes archivos HTML, CSS y JS.

index.html

<head>
  <title>Netbeast Bulb Plugin</title>
  <link rel="stylesheet" href="bulb.css" media="screen" charset="utf-8">
</head>

<body>
  <div class="top-decoration"></div>
  <div id="plugin front-end">
  </div>
  <div class="bulb-container small">
    <div class="bulb light">
      <div id="bulb">
        <div class="bulb top"></div>
        <div class="bulb middle-1"></div>
        <div class="bulb middle-2"></div>
        <div class="bulb middle-3"></div>
        <div class="bulb bottom"></div>
      </div>
      <div id="base">
        <div class="screw-top"></div>
        <div class="screw a"></div>
        <div class="screw b"></div>
        <div class="screw a"></div>
        <div class="screw b"></div>
        <div class="screw a"></div>
        <div class="screw b"></div>
        <div class="screw c"></div>
        <div class="screw d"></div>
      </div>
    </div>
  </div>
    <div class="code-container">
      <pre><i class="txt-red">beast</i>(<i class="txt-green">'lights'</i>).<i class="txt-blue">set</i>({
  <i class="txt-green">color</i>: <i class="txt-green">"<input id="color" type="text" class="color" name="color" value="00fea5">"</i>,
  <i class="txt-green">power</i>: <i class="txt-green">"<input id="power" type="text" class="power" name="power" value="on">"</i>
})</pre>
    <button id="run-btn">
      RUN
    </button>
  </div><!-- wrapper -->

  <!-- declares bulb features and methods -->
  <script type="text/javascript" src="bulb.js"></script>
  <!-- real time comms library -->
  <script type="text/javascript" src="socketio.js"></script>
  <!-- simulates hardware communication -->
  <script type="text/javascript" src="hw-api.js"></script>
</body>

bulb.css

section {
  float: left;
  padding: 20px 50px 20px 50px;
}

.bulb-light {
  border: 0;
  background: transparent;
  margin: 0 auto !important;
  padding: 0 !important;
  display: block;
  z-index: 1;
}

#bulb { opacity: 1; z-index: 3; display: block;}

.bulb.top {
  border: 0;
  width: 300px;
  height: 300px;
  margin: 0 auto;
  padding: 0;
  border-radius: 999px;
  background: #E7E7E7;
}

.bulb.middle-1 {
  margin: -75px auto 0 auto;
  width: 190px;
  border-left: 35px solid transparent;
  border-right: 35px solid transparent;
  border-top: 55px solid #E7E7E7;
}

.bulb.middle-2 {
  margin: -22px auto 0 auto;
  width: 178px;
  border-left: 19px solid transparent;
  border-right: 19px solid transparent;
  border-top: 50px solid #E7E7E7;
}

.bulb.middle-3 {
  margin: -20px auto 0 auto;
  width: 182px;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 30px solid #E7E7E7;
}

.bulb.bottom {
  width: 184px;
  height: 65px;
  margin: -8px auto 0 auto;
  padding: 0;
  border-radius: 0 0 999px 999px;
  background: #E7E7E7;
}

#base { position:relative; z-index: 2; }

.screw {
  transform: rotate(-3deg);
  -ms-transform: rotate(-3deg);
  -webkit-transform: rotate(-3deg);
  padding: 0;
}

.screw-top {
  margin: -18px auto -4px auto;
  padding: 0;
  width: 132px;
  height: 0;
  border-left: 15px solid transparent;
  border-right: 15px solid transparent;
  border-top: 21px solid #D3D3D3;
  border-radius: 999px;
}

.screw.a {
  background: #DDD;
  width: 150px;
  height: 15px;
  border-radius: 999px;
  margin: -1px auto 0px;
}

.screw.b {
  background: #D9D9D9;
  width: 135px;
  height: 15px;
  margin: -1px auto 0px;
}

.screw.c {
  margin: -1px auto 0px;
  width: 78px;
  height: 0;
  border-left: 30px solid transparent;
  border-right: 30px solid transparent;
  border-top: 20px solid #DDD;
  border-radius: 8px;
}

.screw.d {
  margin: 0 auto;
  width: 15px;
  height: 0;
  border-left: 30px solid transparent;
  border-right: 30px solid transparent;
  border-top: 15px solid #444;
}

.on #light {
  -moz-opacity: 1;
  -khtml-opacity: 1;
  opacity: 1;
}

.bulb.top, .bulb.bottom {
  transition: all 0.5s ease-in-out;
}

.bulb.middle-1, .bulb.middle-2, .bulb.middle-3 {
  transition: all 0.5s ease-in-out;
}
Con estos archivos HTML y CSS, ya se debería poder ver una forma de bombilla en tu navegador. ¡Abre el archivo HTML para ver en vivo! ¿Está funcionando? Bien, ahora vamos a darle algunas funciones.

bulb.js

Este archivo imitará el comportamiento de una bombilla con un simple clic de encendido. Al hacer click para apagarla, establecerás un par de funciones que serán utilizadas un poco después para cambiar su color a través de Netbeast.
var color = document.getElementById('color')
var power = document.getElementById('power')
var bulb = document.getElementById('bulb')
var button = document.getElementById('run-btn')
var light = document.getElementById('light')

button.onclick = function toggleBulbState () {
  changeBulbParams({ color: color.value, power: power.value })
}

function setBulbParams (params) {
  if (params.power === 'off') {
    params = { color: 'E7E7E7' }
  }
  console.log('set params', params)

  var bulb_parts = ['.bulb.middle-1', '.bulb.middle-2', '.bulb.middle-3']

  document.querySelector('.bulb.top').style.boxShadow = '0px 0px 98px #' + params.color

  document.querySelector('.bulb.top').style.backgroundColor = params.color
  document.querySelector('.bulb.bottom').style.backgroundColor = params.color
  bulb_parts.forEach(function (className) {
    document.querySelector(className).style.borderTopColor = params.color
  })
}

function changeBulbParams (params) {
  console.log('change params', params)
  /* Overwrite html fields if necessary */
  color.value = params.color || color.value
  power.value = params.power || power.value
  setBulbParams({color: color.value, power: power.value})
}
Luego, todo comenzará a tener sentido: los campos y el botón de ejecución, ¡ahora podrás comenzar a probar los diferentes colores de tu nueva bombilla virtual! Sin embargo, la razón por la que vinimos hasta aquí es para hacer otro dispositivo de nuestro ecosistema del Internet de las Cosas.

hw-api.js

El último de front-end JS realizado por nosotros mismos, simula una conexión inalámbrica con el servidor, como lo haría un WiFi o bombilla Bluetooth con su mando a distancia, tales como un teléfono, un servidor o un hub. ¡Es la interfaz que el código plugin utilizará para controlarlo!
var socket = io.connect()

socket.on('connect', function () { console.log('ws:// bulb is online') })
socket.on('disconnect', function () { console.log('ws:// connection with bulb lost') })

socket.on('set', function (params) {
   changeBulbParams(params) // uses functions from bulb.js!
})

socket.on('get', function () {
  const params = { power: power.value, color: color.value }
  socket.emit('params', params)
})
Por último, necesitamos que la biblioteca WebSocket sea incluida en nuestro HTML para que la interfaz esté lista. Puedes copiar el código fuente de https://raw.githubusercontent.com/netbeast/bulb-plugin/master/public/socketio.js y pegarlo en un archivo llamado socketio.js. Desde un terminal con curl o wget, lo puedes hacer así de simple:
curl https://raw.githubusercontent.com/netbeast/bulb-plugin/master/public/socketio.js  > public/socketio.js
Tendríamos ahora una estructura de archivos que se ve así:
myplugin
├── README.md
├── index.js
├── package.json
├── public
│   ├── bulb.css
│   ├── bulb.js
│   ├── hw-api.js
│   ├── index.html
│   └── socketio.js
└── test.js

Backend

Ahora vamos a implementar la interfaz con el dispositivo y registrarlo en el motor Netbeast. Éste escuchará los websockets para detectar si alguna bombilla se ha instalado en la red, y luego hará un POST a la API del panel de control para que los nuevos recursos estén disponibles.
Por eso, vamos a echar un vistazo a los archivos que generamos antes:

package.json

Este archivo contiene todas las dependencias y la información necesaria para ejecutar tu aplicación. Netbeast utiliza el habitual package.json también para recuperar cierta información, como el nombre o el tipo. ¡Es importante especificar que este paquete es un plugin!
{
  "name": "myplugin",
  "version": "0.0.0",
  "description": "Netbeast plugin for... <your description>",
  "main": "index.js",
  "netbeast": {
    "bootOnLoad": true,
    "type": "plugin"
  },
  "dependencies": {
    "bluebird": "^3.3.5",
    "body-parser": "^1.15.0",
    "express": "^4.13.4",
    "minimist": "^1.2.0",
    "mocha": "^2.3.2",
    "morgan": "^1.6.1",
    "netbeast": "^1.0.6",
    "socket.io": "^1.4.5",
    "superagent": "^1.8.3"
  },
  "devDependencies": {},
  "scripts": {
    "test": "node test.js",
    "start": "node index.js"
  },
  "repository": {
    "type": "git",
    "url": "GITHUB_REPOSITORY"
  },
  "keywords": [
    "iot",
    "netbeast",
    "plugin"
  ],
  "author": "YOUR_EMAIL",
  "license": "GPL 3",
  "bugs": {
    "url": "ISSUES_CHANNEL"
  },
  "homepage": "HOMEPAGE"
}

index.js

¡Éste es el código que se trae desde el panel de control de Netbeast para lanzar el plugin! Éste tendrá que aceptar el puerto a través de argumentos de línea de comando para saber dónde aceptar peticiones entrantes. Se lanzará como si escribiéramos node myplugin.js --port <a free port number>. ¡Recuerda tomar en cuenta el hashbang al principio! #!/usr/bin/env node.
#!/usr/bin/env node

var io = require('socket.io')()
var express = require('express')
var bodyParser = require('body-parser')

var app = express()

// Netbeast apps need to accept the port to be launched by parameters
var argv = require('minimist')(process.argv.slice(2))

app.use(express.static('public')) // will serve our app in an HTTP server
app.use(bodyParser.json()) // will parse JSON API calls
app.use('/api', require('./plugin')(io)) 

var server = app.listen(argv.port || 31416, function () {
  console.log('Bulb plugin listening at http://%s:%s', server.address().address,
  server.address().port)
})

// we need websockets to push updates to browser view
io.listen(server)
Como se puede ver nos faltaba un archivo por iniciar, el que realmente implementa los controladores socket.io. ¡Nada sofisticado!

plugin.js

var express = require('express')
var netbeast = require('netbeast')

var router = express.Router()
var bulbParams // auxiliar variable, nasty way to transmit changes, but works

module.exports = function (io) {
  io = io
  
  // Create resource that works on lights topic and listens on /api route
  netbeast('lights').create({ app: 'myplugin', hook: '/api' })

  io.on('connection', function () { 
   console.log('ws:// bulb has connected to plugin') 
  })

  io.on('disconnection', function () { 
   console.log('ws:// bulb has disconnected from plugin') 
  })

  io.on('connect_failure', function (err) { console.trace(err) })

  router.post('/', function (req, res) {
    io.emit('set', {
      power: req.body.power,
      color: req.body.color,
    })
    res.status(200).json(req.body)
  })

  router.get('/', function (req, res) {
    io.emit('get')
    var timerReference = setTimeout(function () {
      if (bulbParams) {
        res.json(bulbParams)
      } else {
        res.status(200).json({ error: 'No bulb available' })
      }
    }, 3000)
  })

  return router
}

Inicia tu Aplicación

Ahora es el momento de probar la aplicación. Puedes empaquetarlo todo en un formato tar.gz y luego subir la aplicación a tu panel de control en la sección de arrastrar y soltar http://localhost:8000/install.
beast package # Compresses your app when ran in myplugin dir
¡Voilà! Ahora puedes ir a tus plugins y probarlo. Ve a la sección de red (http://localhost:8000/devices) para ver su funcionamiento y cambiar su color a partir de ahí.
Si algo sale mal o piensas que podrías haber olvidado un detalle, trata de ejecutar localmente con el nodo node index.js, y tal vez será más fácil de depurar que dentro del registro netbeast start.

Publicar su trabajo

Si deseas que la aplicación aparezca en el panel de control de la sección Explore de Netbeast, debes crear un repositorio en GitHub con la aplicación o plug-in de Netbeast, ambos incluidos en la descripción y README.md.
Para encontrar las aplicaciones que hacemos, usa la API de búsqueda de GitHub. Vemos los mismos resultados que aparecen cuando se realiza una solicitud GET a:https://api.github.com/search/repositories?q=netbeast+language:javascript
¡Sabrás que se mostrará tu aplicación, si parece hay!

¿Qué sigue?

Ambos proyectos son de open source y realmente han involucrado a las comunidades. Si deseas comenzar a crear tus propios flujos o nodos al Node-Red, echa un vistazo a su documentación oficial. Sigue los pasos descritos allí y podrás publicar tu propio nodo o flujo en poco tiempo.
Por otro lado, si quieres entrar en el interior Netbeast, puedes seguir su documentación también o echar un vistazo al repositorio del panel. Al usar la API Netbeast, no tienes que centrarte en los dispositivos individuales, marcas o tecnologías, así que dale una oportunidad. Puedes aprender más sobre esto en NetBeast.co y unirte a su Slack channel para discutir Node-RED, IoT o Node.js.
Si deseas instalar esto en una Raspberry Pi, Beagle Bone o servidor antiguo, lo convertirías en un Smart Hub fácil de hackear, ¡sin código! Hay instaladores ya hechos para ellos en ambos sitios.
Feliz hacking.
Artículo original en Toptal.