DEV Community

Cover image for Una interfaz de software para protocolos industriales
Brian Michel
Brian Michel

Posted on

Una interfaz de software para protocolos industriales

La integración de dispositivos de campo tropieza casi siempre con el mismo
obstáculo: los equipos que hay que reunir en una misma vista no hablan el
mismo protocolo, y ninguno de esos protocolos fue diseñado pensando en los
demás.

Una instalación industrial rara vez habla un solo protocolo. Conviven
medidores Modbus TCP que solo responden cuando reciben una petición,
sensores que publican por MQTT cada vez que tienen algo que comunicar y
equipos que exponen sus datos por OPC UA, con su propio modelo de
información y su propio sistema de tipos. Todos ellos deben aparecer en la
misma vista.

La dificultad no es de comunicación —cada protocolo está bien documentado
y dispone de bibliotecas maduras—, sino de representación. El registro
Modbus llega como 16 bits sin unidad ni marca de tiempo, y el instante de
la lectura es el instante en que se preguntó. El mensaje MQTT llega como
JSON con un timestamp generado en el dispositivo. El nodo OPC UA llega con
su tipo declarado, su marca de tiempo de origen y su código de calidad.
Son tres representaciones distintas de la misma idea —una medición— y la
capa de presentación termina con una rama de código por cada una.

La capa intermedia

La solución es una capa entre la vista y los dispositivos: una interfaz
de comunicación que exponga un contrato único, independiente del
protocolo que haya debajo. Quien la consuma —una vista, un panel de
supervisión, un servicio de alarmas— recibe siempre el mismo tipo de
dato, con independencia de si procede de Modbus, de MQTT o de OPC UA. La
traducción ocurre por debajo de la interfaz, dentro de cada canal, y no se
propaga hacia arriba.

Comparación entre una aplicación acoplada a cada protocolo y la misma aplicación sobre una capa de abstracción

Sin capa intermedia, cada protocolo llega hasta la lógica de negocio. Con
ella, la aplicación programa contra un contrato único y la diferencia se
resuelve por debajo.

Este planteamiento aporta, entre otras ventajas:

  • El programador de aplicación deja de aprender protocolos. No necesita saber que Modbus numera los registros desde 1 pero los direcciona desde 0, ni cómo se negocia un QoS en MQTT.
  • La información queda centralizada sin casarse con un fabricante. Pueden convivir marcas y protocolos distintos en la misma instalación, porque la diferencia se absorbe en el canal y no en la aplicación.
  • Se prescinde del hardware traductor. Los gateways que convierten todo a un protocolo común suponen un coste, ocupan espacio en el tablero y añaden un punto de fallo. Si la traducción es software, desaparecen.

Ese último punto merece detenerse, porque va contra lo que se hace
habitualmente. Cuando se busca cómo integrar Modbus con MQTT, lo que se
encuentra son puentes: un microcontrolador con su sistema operativo en
tiempo real que consulta los registros por un lado y publica en temas por
el otro, o un gateway comercial que unifica varios protocolos en el
tablero. Funcionan, están bien documentados, y son la respuesta estándar
del sector.

La diferencia es dónde se pone la traducción. Un puente la pone en un
equipo: hay que comprarlo, alimentarlo, configurarlo y mantenerlo, y su
período de consulta es un parámetro suyo que la aplicación no ve ni
controla. Una interfaz la sitúa en el código de la propia aplicación, y
entonces ese período —y las demás decisiones de compromiso— dejan de estar
ocultas en el firmware de un equipo y pasan a ser código que se puede
leer, ajustar y versionar.

1. La interfaz

El primer paso es acotar el conjunto mínimo de operaciones: aquellas que
cualquier canal necesita, con independencia del protocolo. En una primera
aproximación son cuatro: conectar, desconectar, leer y escribir.

public interface IDeviceChannel : IAsyncDisposable
{
    Task ConnectAsync(CancellationToken ct = default);
    Task DisconnectAsync(CancellationToken ct = default);

    Task<Reading> ReadAsync(DeviceData data, CancellationToken ct = default);
    Task WriteAsync(DeviceData data, object value, CancellationToken ct = default);
}
Enter fullscreen mode Exit fullscreen mode

Quien consume esta interfaz dispone de esos métodos y nada más: desconoce
la implementación de cada protocolo, la forma de establecer la conexión y
los tipos de dato nativos que maneja cada uno.

Conviene notar que esos métodos no son todos de la misma naturaleza.
ReadAsync y WriteAsync son operaciones sobre datos: entran y salen
valores. ConnectAsync y DisconnectAsync pertenecen al ciclo de vida:
no producen datos, sino que establecen y liberan el enlace, junto con los
recursos asociados —el socket, la sesión con el intermediario, las
suscripciones abiertas—. Y el ciclo de vida es donde reside la
complejidad real: reconexión, expiración de plazos, el tratamiento de una
escritura pendiente cuando el enlace se interrumpe, o qué debe ocurrir con
una suscripción activa cuando se cierra el canal.

2. Dos modelos de comunicación diferentes

Llegados a este punto aparece la cuestión que decide si el diseño
funciona o se convierte en una capa que estorba: los protocolos no son
equivalentes.

La diferencia está en quién toma la iniciativa. En Modbus el dispositivo
es pasivo: no comunica nada hasta que se le pregunta, y para saber si un
valor ha cambiado hay que volver a preguntar. A ese preguntar de forma
repetida se le llama sondeo (polling). En MQTT ocurre lo contrario:
el dispositivo publica cuando tiene algo que comunicar y el intermediario
lo reparte entre quienes se hayan suscrito; la aplicación no pregunta,
solo espera. Es lo que se conoce como notificación o push.

No es una diferencia de formato que se resuelva con un conversor, y una
interfaz debe decidir de qué lado se sitúa:

  • Si el contrato es ReadAsync(), se impone el sondeo sobre un protocolo que ya notificaba. El sensor MQTT publica un valor a las 10:00:03 y la aplicación lo lee a las 10:00:10: se han introducido siete segundos de latencia y se han perdido los valores intermedios que el dispositivo sí envió.
  • Si el contrato es SubscribeAsync(), hay que construir la notificación donde no existe. El canal Modbus queda obligado a sondear internamente y emitir un valor cuando detecta un cambio. Funciona, pero el período de sondeo queda escondido en la implementación, y el consumidor cree recibir los cambios en el momento en que se producen cuando en realidad los recibe cada 500 ms.

Ninguna de las dos opciones es gratuita. La segunda suele ser preferible
—resulta más sencillo simular la notificación sobre un protocolo de
consulta que a la inversa, y no degrada a los protocolos que ya
notificaban— pero exige que el período de sondeo forme parte del contrato
y no sea un detalle enterrado en la implementación. En C# el mecanismo
natural para una suscripción es IAsyncEnumerable<Reading>, y el período
cabe en la propia firma:

public interface IDeviceChannel : IAsyncDisposable
{
    Task ConnectAsync(CancellationToken ct = default);
    Task DisconnectAsync(CancellationToken ct = default);

    Task<Reading> ReadAsync(DeviceData data, CancellationToken ct = default);
    Task WriteAsync(DeviceData data, object value, CancellationToken ct = default);

    IAsyncEnumerable<Reading> SubscribeAsync(
        DeviceData data,
        TimeSpan period,
        CancellationToken ct = default);
}
Enter fullscreen mode Exit fullscreen mode

period expresa una sola cosa: cada cuánto quiere el consumidor tener
noticias de ese dato. Si indica dos segundos, está diciendo que no acepta
enterarse de un cambio con más de dos segundos de retraso.

Cada canal cumple ese compromiso con los medios de su protocolo, y son
distintos. El canal Modbus, que no dispone de notificación, consulta al
esclavo con esa cadencia: cada dos segundos sale una petición por el
enlace, cambie el valor o no. El canal MQTT no consulta nada, porque el
intermediario ya le entrega las publicaciones conforme llegan; si el
sensor publica cada medio segundo, el consumidor recibe noticias cada
medio segundo y el período no llega a intervenir. Solo actúa en el caso
contrario, cuando transcurren los dos segundos sin publicación alguna: un
dispositivo que deja de emitir no genera ningún evento, y sin ese plazo el
silencio de un sensor averiado sería indistinguible del de un proceso
estable.

Un mismo parámetro y dos comportamientos opuestos —en un caso marca cada
cuánto preguntar, en el otro cuánto esperar antes de dar señales—, y esa
es exactamente la razón de que exista. Unificar no consiste en que ambos
canales hagan lo mismo, porque no pueden: consiste en que ambos ofrezcan
la misma garantía al consumidor por los medios que cada protocolo permite.
Quien consume los datos no sabe cuál de los dos canales tiene debajo, y ya
no lo necesita, porque lo que le importa —cada cuánto tendrá noticias—
está escrito en el contrato en lugar de enterrado en la implementación.

3. Lo que cada protocolo no implementa

Todo lo anterior es diseño sobre el papel. Las consecuencias aparecen al
implementar el contrato dos veces, porque entonces se hace evidente que
ninguno de los dos protocolos lo cubre entero, y que lo que le falta a
uno es justo lo que el otro resuelve de fábrica.

En Modbus la lectura es literalmente el protocolo y no requiere trabajo
adicional; lo que no existe es la suscripción. En MQTT ocurre lo inverso:
la notificación es el protocolo, y no hay ningún interlocutor al que
consultar el valor actual. Como el contrato exige las cinco operaciones,
cada canal debe construir la mitad que su protocolo no le da.

El canal Modbus construye la notificación

Sin mecanismo nativo de notificación, la única salida es sondear. El canal
consulta el dato con la cadencia que indica period, lo compara con el
valor anterior y emite una lectura solo cuando ha cambiado:

while (!ct.IsCancellationRequested)
{
    Reading current = await ReadAsync(data, ct);

    if (!ValuesEqual(current.Value, lastValue))
    {
        lastValue = current.Value;
        yield return current;
    }

    await Task.Delay(period, ct);
}
Enter fullscreen mode Exit fullscreen mode

El bucle funciona, con dos ajustes que conviene mencionar. El retardo se
aplica después de la lectura, de modo que cada vuelta cuesta period
más lo que haya tardado la consulta: con un período de 500 ms y una
lectura de 20, el consumidor recibe noticias cada 520 y sigue creyendo que
son 500; basta medir la lectura y esperar solo los 480 restantes para que
el ciclo dure lo prometido. Y como Modbus TCP admite una transacción a la
vez sobre un mismo socket, este sondeo comparte enlace con las lecturas
que el consumidor sigue pidiendo por su cuenta, así que todas las
transacciones deben ejecutarse de una en una para que las tramas no se
entrelacen.

Ninguno de los dos ajustes es costoso. Lo que conviene retener es de dónde
salen: nada de esto existía en Modbus, y ambos aparecen únicamente porque
el canal se comprometió a avisar de los cambios. El consumidor pidió una
suscripción, y el precio de esa suscripción queda del lado del canal,
invisible para él.

El canal MQTT construye la lectura

En el canal MQTT el problema es el inverso. Un valor solo se conoce en el
instante en que el dispositivo lo publica, y ante una consulta a destiempo
no hay nada que devolver. La salida es recordar: suscribirse al conectar y
guardar cada publicación conforme llega.

private Task OnMessageReceived(MqttApplicationMessageReceivedEventArgs e)
{
    byte[] payload = e.ApplicationMessage.Payload.ToArray();

    lock (_cacheLock)
        _lastReadings[e.ApplicationMessage.Topic] = Reading.Of(payload, _time.GetUtcNow());

    return Task.CompletedTask;
}
Enter fullscreen mode Exit fullscreen mode

Cada publicación se almacena junto al instante en que fue recibida, y
ReadAsync la recupera de ese almacén: localiza el tema y devuelve el
último valor registrado. La lectura no interroga al dispositivo —eso sigue
siendo imposible— sino a la memoria del canal.

Resta un problema de interpretación. MQTT transporta una secuencia de
bytes sin significado declarado: un tema puede contener una temperatura,
un estado de marcha o una cadena de texto, y todos llegan al canal en la
misma forma. Determinar el tipo de cada uno exige información que el
protocolo no transmite, de modo que es el propio dato quien la declara y
el canal quien la aplica en el momento de la lectura:

string text = Encoding.UTF8.GetString(payload);

return data.PayloadType switch
{
    MqttPayloadType.Number  => double.Parse(text, CultureInfo.InvariantCulture),
    MqttPayloadType.Boolean => bool.Parse(text),
    MqttPayloadType.Text    => text,
};
Enter fullscreen mode Exit fullscreen mode

La conversión ocurre en la lectura y no al recibir el mensaje, y esa
decisión importa: el mismo tema podría interpretarse de formas distintas
según quién lo consulte, de modo que el almacén conserva los bytes tal
como llegaron y cada consulta los traduce según lo que declare su dato.

4. Cuando la misma llamada significa dos cosas

Lo anterior era una diferencia de interpretación, y se resolvía
declarando el tipo junto al dato. Hay una más profunda, que ninguna
declaración arregla, y es la que mejor enseña dónde está el límite real
de este tipo de abstracción.

Un caché que solo guarda el último valor tiene un problema: si el
consumidor lee dos veces y el dispositivo no ha publicado en medio,
recibe el mismo dato otra vez sin poder distinguir si es nuevo o
repetido. Una forma de resolverlo es vaciar la entrada al leerla, de
modo que la lectura entrega el valor pendiente y deja el hueco vacío. Así
se resolvió en una implementación anterior de este mismo canal:

public Result ReadValue(DeviceData data, out Reading reading)
{
    lock (_lastPayloads)
    {
        if (_lastPayloads.TryGetValue(data.Topic, out var payload))
        {
            reading = new Reading(payload);
            _lastPayloads[data.Topic] = null;   // la lectura consume el valor
            return Result.Success();
        }

        reading = Reading.Empty;
        return Result.Failure("No hay valor disponible para ese dato");
    }
}
Enter fullscreen mode Exit fullscreen mode

Conviene examinar la consecuencia. En el canal Modbus, ReadAsync es
idempotente: dos consultas seguidas devuelven el mismo valor, porque se
interroga al dispositivo y el dispositivo sigue valiendo lo que vale. En
el canal MQTT, la segunda lectura devuelve vacío aunque nada haya
cambiado en la instalación.

Y no es que una implementación esté mal. Es que la misma firma está
sosteniendo dos operaciones que ni siquiera son del mismo tipo:

  • en Modbus, ReadAsync significa "dame el estado actual";
  • en MQTT, significa "dame lo que haya llegado desde la última vez que pregunté".

Lo primero es consultar un valor. Lo segundo es consumir de una cola de
un solo elemento. El contrato no distingue entre ambas cosas —no tiene
con qué—, así que el consumidor recibe una u otra según qué canal le
esté usando, sin manera de saber cuál. El consumidor acaba escribiendo un
if (valor == null) para tratar el caso excepcional, y ese if es puro
sondeo: en Modbus no se cumple nunca y en MQTT se cumple la mitad de las
veces.

En ese punto la abstracción deja de ser una comodidad y pasa a ser una
fuente de error. No porque el código esté mal escrito, sino porque el
contrato daba por supuesto que los cuatro métodos significaban lo mismo
en ambas implementaciones, y ese supuesto no se sostiene.

La salida no es renunciar a la interfaz: es hacer explícito lo que el
contrato estaba escondiendo. Que Reading diga cuándo se generó el
valor y si es el mismo de antes; que una lectura sin dato disponible sea
un estado previsto y con nombre, no un fallo genérico; que el consumidor
pueda saber si está ante un valor consultado o ante uno recibido. Cada
una de esas decisiones ensancha el contrato y le quita elegancia —y cada
una sustituye una suposición silenciosa por algo que se puede leer en la
firma.

Cómo queda resuelto

El canal del repositorio ya no vacía la entrada. La lectura localiza el
tema y devuelve lo que haya, sin tocar el almacén:

lock (_cacheLock)
{
    if (!_lastReadings.TryGetValue(mqttData.Topic, out Reading? cached))
        return Task.FromResult(Result<Reading>.Success(Reading.Empty(_time.GetUtcNow())));

    return Task.FromResult(Decode(cached, mqttData));
}
Enter fullscreen mode Exit fullscreen mode

Dos líneas que cambian y tres consecuencias. La primera es que ReadAsync
vuelve a ser idempotente: dos consultas seguidas devuelven lo mismo, igual
que en Modbus, y la lectura deja de ser una operación que altera el canal.

La segunda es que la pregunta que motivaba el vaciado —si el valor es
nuevo o repetido— la responde ahora la marca de tiempo, que se sella al
recibir la publicación y no al leerla:

Reading reading = Reading.Of(payload, _time.GetUtcNow());
Enter fullscreen mode Exit fullscreen mode

Al consumidor le basta comparar el Timestamp con el de la lectura
anterior: si no ha variado, no ha habido publicación nueva. Es la misma
información que daba el hueco vacío, sin destruir el valor para
obtenerla, y además dice cuánto hace en lugar de limitarse a un sí o un
no.

La tercera es que la ausencia de valor deja de viajar como fallo. Antes,
un tema sobre el que nadie había publicado devolvía Result.Failure y
resultaba indistinguible de un enlace roto; ahora devuelve un resultado
correcto con un Reading vacío, y son dos preguntas separadas: Result
responde a si se pudo hablar con el dispositivo, HasValue a si hay algo
que entregar.

Con esas tres piezas, ReadAsync significa lo mismo en los dos canales
—entregar el último estado conocido junto al instante en que se conoció—
y el if del consumidor deja de ser un parche para el caso excepcional.
Conviene señalar que el problema no desaparece del todo, sino que cambia
de sitio: quien quiera saber si ya había visto ese valor debe guardar el
último Timestamp recibido. La diferencia es que esa decisión pasa a
tomarla el consumidor, que es el único que sabe qué antigüedad le sirve.

5. El contrato completo

Con lo visto hasta aquí, el contrato ya funciona sobre dos protocolos
opuestos. Lo que falta es cerrar los supuestos que quedaban implícitos, y
todos se cierran del mismo modo: llevando al contrato lo que el
consumidor necesita saber y hasta ahora quedaba en la implementación
.
Ese trabajo se concentra en dos lugares, el valor que se entrega y el dato
que se solicita.

Lo que el contrato declara sobre cada valor

ReadAsync devuelve un valor obtenido de formas muy distintas: en Modbus
es el resultado de interrogar al esclavo en ese momento; en MQTT, la
última publicación recibida, que puede ser de hace cinco minutos. Esa
diferencia no se puede eliminar, pero sí se puede hacer explícita en el
propio valor:

public sealed record Reading
{
    public object? Value { get; }
    public DateTimeOffset Timestamp { get; }
    public bool HasValue => Value is not null;
}
Enter fullscreen mode Exit fullscreen mode

Tres miembros que resuelven tres cuestiones. El instante en que se
obtuvo
permite al consumidor juzgar la vigencia con su propio criterio,
que es el único válido: veinte segundos son inmejorables para la
temperatura de una nave e inaceptables para un enclavamiento, y el canal
no sabe cuál de los dos casos tiene delante. La ausencia de valor deja
de confundirse con un fallo: que nadie haya publicado todavía en un tema
no significa que el enlace esté roto, de modo que Result responde a si
se pudo hablar con el dispositivo y HasValue a si hay algo que entregar.
Y el hecho de que la lectura no altere nada mantiene la operación
idempotente: el almacén conserva el valor tras entregarlo, y la novedad se
deduce comparando la marca de tiempo, no vaciando la entrada.

Con estos tres datos el consumidor puede escribir la misma lógica para
cualquier protocolo. Deja de preguntarse de dónde viene el valor y pasa a
preguntarse si le sirve, que es lo único que necesitaba saber.

Lo que el contrato declara sobre cada dato

Queda la información que los protocolos transportan pero no explican.
Modbus entrega dieciséis bits sin decir qué representan: que dos registros
consecutivos formen un número decimal, un entero con signo o dos valores
independientes está en el manual del fabricante, y el orden en que se
disponen esas dos mitades varía según el equipo. MQTT entrega bytes con el
mismo silencio.

Esa información existe siempre; simplemente no viaja en la trama. Y si no
viaja en la trama, se declara donde el canal pueda consultarla:

public sealed record ModbusDeviceData : DeviceData
{
    public required ModbusRegisterType RegisterType { get; init; }
    public required ushort StartAddress { get; init; }
    public required ModbusDataType DataType { get; init; }
    public ModbusWordOrder WordOrder { get; init; } = ModbusWordOrder.HighWordFirst;
}
Enter fullscreen mode Exit fullscreen mode

El resultado es que Reading.Value deja de ser una secuencia de bits para
ser un número, un valor lógico o un texto, con independencia del protocolo
que lo haya traído. La declaración es específica de Modbus, pero vive en
una subclase que solo el canal conoce; el código que dibuja la tabla nunca
la ve.

El mismo criterio resuelve un caso más delicado. Modbus impide escribir en
un registro de entrada, porque el protocolo lo define de solo lectura;
pero una bobina que la instalación no permite accionar —un enclavamiento
de seguridad— es escribible para el protocolo y no debe serlo para la
aplicación. Esa restricción tampoco viaja en ninguna trama, así que se
declara junto al dato, y el canal la hace cumplir antes de tocar el
enlace:

public abstract record DeviceData
{
    public required string Name { get; init; }
    public DataAccess Access { get; init; } = DataAccess.ReadWrite;
}
Enter fullscreen mode Exit fullscreen mode

El contrato resultante

Las operaciones siguen siendo las mismas. Lo que ha cambiado es que ahora
describen con precisión lo que devuelven:

public interface IDeviceChannel : IAsyncDisposable
{
    Guid DeviceId { get; }
    bool IsConnected { get; }

    Task<Result> ConnectAsync(CancellationToken ct = default);
    Task<Result> DisconnectAsync(CancellationToken ct = default);

    Task<Result<Reading>> ReadAsync(DeviceData data, CancellationToken ct = default);
    Task<Result> WriteAsync(DeviceData data, object value, CancellationToken ct = default);

    IAsyncEnumerable<Reading> SubscribeAsync(
        DeviceData data,
        TimeSpan period,
        CancellationToken ct = default);
}
Enter fullscreen mode Exit fullscreen mode

Eso es cuanto ve quien consume los datos. Debajo hay dos implementaciones
sin nada en común —una serializa transacciones sobre un socket y sondea en
bucle; la otra mantiene un almacén alimentado por eventos del
intermediario— y ninguna de esas dos realidades se filtra hacia arriba. El
código que lee una temperatura por Modbus y una ocupación por MQTT es el
mismo código recorriendo la misma lista.

Añadir un tercer protocolo consiste en implementar esta interfaz una vez
más: ni los canales existentes ni el consumidor se modifican. Y las
preguntas que hay que responder al hacerlo son las que respondieron estos
dos —qué parte del contrato trae el protocolo de fábrica, qué hay que
construirle, y dónde queda declarado lo que no transmite.

Hay además una consecuencia que conviene no pasar por alto. Si todo lo que
distingue a un dato —dónde se encuentra, cómo se interpreta, si admite
escritura— cabe en un DeviceData, la instalación completa puede
describirse en un archivo de configuración en lugar de en código. Un
sensor nuevo es una entrada más en ese archivo, y el único punto del
programa que menciona un protocolo es la función que decide qué subclase
construir para cada entrada. Por encima de ella no queda ni una referencia
a los canales.

Conclusión

La implementación de un contrato único sobre Modbus TCP y MQTT confirma
que la unificación es viable incluso entre protocolos sin elementos
comunes, y delimita con precisión su alcance y su coste. Del recorrido
anterior se desprenden las siguientes conclusiones:

  • La unificación de formatos no presenta contrapartidas. Que un
    registro de dieciséis bits y una publicación MQTT se representen
    mediante un mismo tipo es una operación de traducción sin consecuencias
    para el consumidor.

  • La unificación de semánticas tiene siempre un coste. Equiparar la
    consulta y la notificación exige construir en cada canal la operación
    que su protocolo no proporciona, lo que introduce un sondeo que compite
    por el enlace, un almacén que debe mantenerse y una conversión que debe
    declararse.

  • Ese coste no se elimina omitiéndolo del contrato. Cuando no se
    declara, se manifiesta como un período de muestreo no elegido por el
    consumidor, como un código de error con significados distintos según la
    implementación, o como una operación de lectura que altera el estado del
    canal.

  • La resolución en software traslada las decisiones a quien integra.
    Frente al gateway o la plataforma SCADA, que fijan de antemano los
    protocolos admitidos y los compromisos aplicables, aquí incorporar un
    protocolo consiste en implementar la interfaz una vez más y los valores
    de compromiso se ajustan a cada instalación.

La automatización industrial y el desarrollo de software proceden de
tradiciones distintas: la primera razona en registros, esclavos y tiempos
de ciclo; la segunda, en contratos, tipos y dependencias. Una interfaz de
estas características constituye el punto de contacto entre ambas, y es
también donde se concentra el trabajo de diseño.

El código

El contrato descrito, los dos canales y un consumidor que los emplea sin
código específico de protocolo están disponibles en
multi-protocol-device-interface.

Incluye una aplicación de consola desde la que se ejecutan las cinco
operaciones del contrato, con dispositivos simulados dentro del propio
proceso o contra software de simulación externo.

Lectura complementaria

El sistema en el que se integra una capa de comunicación como esta abarca
bastante más que el contrato con el dispositivo. Para el lector interesado
en esa perspectiva, Medina Rodríguez, Pérez Villanueva y Prieto Moreno
desarrollan una capa completa de Computación al Borde para un sistema de
gestión de operaciones de fabricación, en la que una interfaz de
comunicación resuelve la conexión con las capas superior e inferior.

Medina Rodríguez, Z., Pérez Villanueva, H. D. y Prieto Moreno, A.
(2024). Propuesta de arquitectura para la capa de Computación al Borde
en entornos de Industria 4.0. Revista Científica de Ingeniería
Electrónica, Automática y Comunicaciones
, 45(2). ISSN 1815-5928.
https://dialnet.unirioja.es/servlet/articulo?codigo=9781718

Top comments (0)