Aller au contenu

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 :

local iec104 = require("iec104_1")

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. t2 doit etre strictement inférieur à t1. t3 est la période des trames de test (TESTFR) en l'absence de trafic.
  • k / w : Fenetres de controle de flux. k limite le nombre de trames I émises non acquittées ; w déclenche un acquittement apres w trames recues.
  • auto_reconnect : Reconnexion automatique en cas de perte de lien, avec backoff exponentiel borné par reconnect_backoff_min et reconnect_backoff_max.
  • auto_gi_on_reconnect : Lance automatiquement une interrogation générale (GI) sur common_address apres 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_verify dé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) : si true, 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").