Module iec104
Le module iec104 implémente un client IEC 60870-5-104 (télécontrôle sur TCP/IP) pour l'agent Alemca. Il est destiné à la communication avec des RTU solaires/éoliennes, des onduleurs centraux et des controleurs de centrale (plant controllers).
Le module gère la connexion, la surveillance des données spontanées, l'interrogation générale (GI) et de compteurs, ainsi que l'envoi de commandes (simple, double, consigne, synchronisation d'horloge).
Licence LGPL v3
Le module s'appuie sur une bibliothèque IEC 104 vendorée sous licence LGPL v3. Voir vendor_iec104/NOTICE dans les sources pour l'attribution.
Import
Pour importer le module en Lua :
Configuration YAML
Le module iec104 nécessite une configuration dans le fichier YAML de l'agent :
iot:
modules:
iec104:
- name: iec104_1 # nom unique de l'instance
host: 192.168.1.50 # adresse du RTU (requis)
port: 2404 # port TCP (défaut: 2404)
common_address: 1 # adresse commune (ASDU), pour la GI auto
connect_timeout: 5 # timeout de connexion en secondes
debug: false # active les traces détaillées
# --- Paramètres temporels IEC 104 (secondes) ---
t0: 30 # timeout d'établissement de connexion
t1: 15 # timeout d'acquittement (ACK)
t2: 10 # doit etre < t1
t3: 20 # période des trames de test (TESTFR)
# --- Fenetres de flux ---
k: 12 # nombre max de trames I en attente d'ACK
w: 8 # ACK apres w trames recues
# --- Reconnexion ---
auto_reconnect: true # reconnexion automatique
reconnect_backoff_min: 1 # backoff minimal en secondes
reconnect_backoff_max: 30 # backoff maximal en secondes
auto_gi_on_reconnect: true # interrogation générale apres (re)connexion
buffer_size: 5000 # taille de la file d'évènements
# --- TLS (optionnel) ---
tls:
enabled: false
ca_file: /etc/alemca/certs/ca.pem
cert_file: /etc/alemca/certs/client.pem
key_file: /etc/alemca/certs/client.key
server_name: plant.example.com
insecure_skip_verify: false
Détails de la configuration
- name : Nom unique de l'instance du module. Utilisé pour l'import en Lua.
- host : Adresse IP ou nom d'hote du RTU distant (requis).
- port : Port TCP du serveur IEC 104 (défaut:
2404). - common_address : Adresse commune d'ASDU utilisée pour l'interrogation générale automatique apres une (re)connexion.
- connect_timeout : Délai maximal pour établir la connexion TCP, en secondes.
- t0 / t1 / t2 / t3 : Paramètres temporels normalisés IEC 60870-5-104.
t2doit etre strictement inférieur àt1.t3est la période des trames de test (TESTFR) en l'absence de trafic. - k / w : Fenetres de controle de flux.
klimite le nombre de trames I émises non acquittées ;wdéclenche un acquittement apreswtrames recues. - auto_reconnect : Reconnexion automatique en cas de perte de lien, avec backoff exponentiel borné par
reconnect_backoff_minetreconnect_backoff_max. - auto_gi_on_reconnect : Lance automatiquement une interrogation générale (GI) sur
common_addressapres chaque (re)connexion, afin de resynchroniser l'état. - buffer_size : Taille de la file interne d'évènements. Quand elle est pleine, les évènements les plus anciens sont abandonnés (compteur
dropped). - tls : Bloc optionnel pour sécuriser le lien (IEC 104 sur TLS).
insecure_skip_verifydésactive la vérification du certificat serveur (à réserver aux tests).
API Lua
Toutes les fonctions retournent (résultat, err) ou err vaut nil en cas de succès et une chaine de caractères en cas d'échec.
Liste des fonctions
| Fonction | Signature Lua | Rôle |
|---|---|---|
connect |
ok, err = iec104.connect() |
Ouvre la connexion vers le RTU. |
disconnect |
ok, err = iec104.disconnect() |
Ferme la connexion. |
isConnected |
bool = iec104.isConnected() |
Indique si le lien est établi. |
recv |
events, err = iec104.recv(max, timeout_ms) |
Récupère les évènements recus. |
generalInterrogation |
ok, err = iec104.generalInterrogation(ca) |
Interrogation générale (GI). |
counterInterrogation |
ok, err = iec104.counterInterrogation(ca) |
Interrogation de compteurs. |
sendSingleCommand |
ok, err = iec104.sendSingleCommand(ca, ioa, bool [, select]) |
Commande simple (C_SC_NA_1). |
sendDoubleCommand |
ok, err = iec104.sendDoubleCommand(ca, ioa, "on"\|"off" [, select]) |
Commande double (C_DC_NA_1). |
sendSetpointFloat |
ok, err = iec104.sendSetpointFloat(ca, ioa, float [, select]) |
Consigne flottante (C_SE_NC_1). |
clockSync |
ok, err = iec104.clockSync(ca [, unix_ts]) |
Synchronisation d'horloge. |
stats |
table = iec104.stats() |
Compteurs de diagnostic. |
| Champs | iec104.name, iec104.type |
Métadonnées du module. |
Cycle de vie
iec104.connect()
Établit la connexion TCP (et TLS si activé) vers le RTU, puis, si auto_gi_on_reconnect est actif, lance une interrogation générale.
local iec104 = require("iec104_1")
local ok, err = iec104.connect()
if not ok then
printError("Connexion IEC 104 échouée : " .. err)
return
end
iec104.disconnect()
Ferme proprement la connexion.
iec104.isConnected()
Retourne true si le lien est actuellement établi, false sinon.
Interrogation
iec104.generalInterrogation(ca)
Envoie une interrogation générale (GI) sur l'adresse commune ca. Le RTU répond en émettant l'état courant de tous ses points ; ces valeurs sont ensuite disponibles via recv().
- Paramètres :
ca(number) — adresse commune d'ASDU.
iec104.counterInterrogation(ca)
Envoie une interrogation de compteurs (lecture des valeurs intégrées / totalisateurs) sur l'adresse commune ca.
Réception des données
iec104.recv(max_events, timeout_ms)
Récupère jusqu'à max_events évènements depuis la file interne. Bloque au maximum timeout_ms millisecondes si la file est vide (défaut: 100 ms), puis draine sans blocage les évènements restants. Retourne toujours une liste (vide si rien n'est arrivé), jamais nil.
- Paramètres :
max_events(number) : nombre maximal d'évènements à retourner.timeout_ms(number, optionnel) : attente maximale pour le premier évènement (défaut: 100).
Chaque évènement est une table :
{
ca = 1, ioa = 100, type = "M_ME_NC_1",
value = 42.5, cot = "spont", ts = 1744711200,
iv = false, nt = false, sb = false, bl = false, ov = false
}
ca/ioa: adresse commune et adresse d'objet d'information.type: identifiant de type IEC (ex.M_ME_NC_1,M_SP_NA_1).value: valeur mesurée ou état.cot: cause de transmission (ex.spont,interrogated).ts: horodatage Unix (secondes).iv/nt/sb/bl/ov: bits de qualité (invalide, non-topical, substitué, bloqué, overflow).
Les évènements systèmes sont signalés avec un type préfixé, par exemple { type = "_reconnect", ca = 1, ts = ... }.
Commandes
iec104.sendSingleCommand(ca, ioa, bool [, select])
Envoie une commande simple (C_SC_NA_1).
ca(number) : adresse commune.ioa(number) : adresse d'objet d'information.bool(boolean) : état à commander (true/false).select(boolean, optionnel) : sitrue, envoie une commande en mode select (SBO — select-before-operate) au lieu d'une exécution directe.
iec104.sendDoubleCommand(ca, ioa, state [, select])
Envoie une commande double (C_DC_NA_1).
state(string) :"on"ou"off". Toute autre valeur retourne une erreur.select(boolean, optionnel) : mode select-before-operate.
iec104.sendSetpointFloat(ca, ioa, value [, select])
Envoie une consigne à virgule flottante (C_SE_NC_1), typiquement pour piloter une limitation de puissance.
value(number) : consigne flottante.select(boolean, optionnel) : mode select-before-operate.
iec104.clockSync(ca [, unix_ts])
Synchronise l'horloge du RTU sur l'adresse commune ca. Sans second argument, utilise l'heure courante ; sinon, utilise l'horodatage Unix fourni.
Diagnostic
iec104.stats()
Retourne une table de compteurs :
{
received = 12034, -- évènements recus
sent = 87, -- trames de commande émises
dropped = 0, -- évènements abandonnés (file pleine)
reconnects = 2, -- nombre de reconnexions
malformed = 0, -- trames mal formées
last_gi_ts = 1744711200 -- horodatage de la dernière GI
}
Types pris en charge (v1)
| Sens | TypeIDs |
|---|---|
| Monitoring (M_*) | 1, 3, 9, 11, 13, 15, 30, 31, 34, 35, 36, 37 |
| Commande (C_*) | 45, 46, 50, 100, 101, 103 |
Exemple complet
local iec104 = require("iec104_1")
local json = require("json")
local alemca = require("alemca")
-- Connexion (une interrogation générale est lancée automatiquement)
local ok, err = iec104.connect()
if not ok then
printError("Connexion IEC 104 échouée : " .. err)
return
end
-- Récupération des évènements accumulés
local events, err = iec104.recv(200, 500)
if events then
local data = {}
for _, ev in ipairs(events) do
if ev.type and ev.type:sub(1, 1) ~= "_" and not ev.iv then
data[tostring(ev.ca) .. "_" .. tostring(ev.ioa)] = ev.value
end
end
alemca.publish(json.encode(data), "iec104_data")
end
-- Exemple de consigne de puissance (select-before-operate)
iec104.sendSetpointFloat(1, 5001, 80.0, true) -- select
iec104.sendSetpointFloat(1, 5001, 80.0, false) -- execute
iec104.disconnect()
Champs Lua associés
iec104.name: Retourne le nom de l'instance du module.iec104.type: Retourne le type du module ("iec104").