Baza wiedzy Sinum

Modbus slave

Tryby pracy Modbus w centrali Sinum

Centrala Sinum obsługuje komunikację w standardzie Modbus w dwóch trybach pracy:

  • Master – centrala komunikuje się z urządzeniami podrzędnymi, takimi jak falowniki, liczniki energii lub inne urządzenia automatyki.

  • Slave – urządzenie nadrzędne (np. system BMS) może odczytywać dane z centrali Sinum.

Centrala może pracować jednocześnie w obu trybach, z zastrzeżeniem, że nie jest możliwe uruchomienie obu trybów na tym samym extenderze.

Domyślnie tryb Modbus Slave jest aktywny na interfejsie TCP, na porcie 502.

Dodatkowo, tryb Slave można włączyć w komunikacji RTU – na extenderze Modbus lub na wbudowanym porcie Modbus centrali.
Należy jednak pamiętać, że dany port może pracować wyłącznie w jednym trybie – Master lub Slave (nigdy jednocześnie w obu).

Centrala Sinum nie posiada stałej listy rejestrów Modbus. Obsługa rejestrów realizowana jest poprzez skrypty Lua, co umożliwia pełne dostosowanie dostępnych rozkazów i struktur danych do wymagań systemu nadrzędnego lub klienta.


Centrala Sinum obsługuje komunikację jako Modbus slave za pomocą dwóch interfejsów:

  • Modbus TCP na porcie 502/tcp,

  • Modbus RTU, przed odpowiednio skofigurowany:

    • ekstender EX-MODBUS z oprogramowaniem w wersji co najmniej 1.4.2

    • wbudowany transceiver, na centrali w wersji oprogramowania co najmniej 1.17.1

Centrala nie rozróżnia żądań pochodzących z Modbus TCP i Modbus RTU.

Konfiguracja Modbus RTU

Transceiver lub ekstender muszą mieć aktywny tryb slave, aby móc przyjmować rozkazy Modbus. Otwórz ustawienia centrali Sinum i przejdź do zakładki urządzenia, a następnie moduły systemowe. Otwórz ustawienia wybranego modułu.nawigacja do opcji modułu

Z listy wybierz tryb slave:
pozycja „tryb slave”

Ustaw parametry komunikacji i naciśnij zapisz.
ustawienia Modbus slave

Zostanie aktywowany tryb slave, a na urządzeniu pojawi się oznaczenie.
ikona slave

Szybki start - obsługa 04 Read Input Registers

Utwórz nową automatyzację typu kod. W edytorze wpisz skrypt:

local slaveId  = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
    -- ustaw dane dla odpowiedzi
    res:data(8)
end)

Zapisz automatyzację. Centrala Sinum będzie odpowiadać na żądanie odczytu rejestru input o dowolnym adresie wartością 8.

Zauważ, że jeśli wyślesz żądanie odczytu więcej niż jednego rejestru, centrala odpowie wyjątkiem 04 SERVER DEVICE FAILURE. Przyczyną jest skrypt, który zwraca zbyt mało wartości dla odpowiedzi.

Aby poprawnie obsłużyć żądanie o dowolnej liczbie rejestrów, należy edytować skrypt, aby przekazać odpowiednią liczbę wartości. Można to zrobić w następujący sposób:

local slaveId  = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
    local data = {}

    -- req:size() zwraca ilość rejestrów, która została zażądana
    for i = 1, req:size() do
        data[i] = i
    end
    res:data(data)
end)

Zapisz zmiany. Centrala będzie teraz odpowiadać na żądanie odczytu dowolnej liczby rejestrów typu input, wysyłając w odpowiedzi kolejne liczby.

Dzięki temu każde żądanie zostanie poprawnie obsłużone, niezależnie od liczby żądanych rejestrów.

Odczytywanie temperatur z czujników temperatury

Utwórz nową automatyzację typu kod. W edytorze wpisz poniższy skrypt:

local slaveId  = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
    local sensor = wtp[req:address()]
    local temp = sensor:getValue("temperature")

    -- przelicz na kod uzupełnień do dwóch
    if temp < 0 then
        temp = temp + 65536
    end

    res:data(temp)
end)

Centrala będzie wysyłać w odpowiedzi temperaturę odczytaną przez czujnik radiowy, którego ID jest równe adresowi rejestru. To przykład minimalny — nie zadziała prawidłowo, gdy:

  • Zostanie zażądana wartość więcej niż jednego rejestru.

  • Urządzenie pod wybranym ID nie istnieje.

  • Urządzenie pod wybranym ID nie jest czujnikiem temperatury.

W przypadku wysłania niepoprawnego adresu (np. odpowiadającego niepoprawnemu ID urządzenia radiowego), centrala może odpowiedzieć wyjątkiem Modbus, sygnalizującym błąd odczytu.

local slaveId  = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
    local ok, sensor = pcall(function ()
        local device = wtp[req:address()]
        assert(device:getValue("type") == "temperature_sensor")
        return device
    end)
    if not ok then
        res:exception(0x2) -- ILLEGAL DATA ADDRESS
    else
        res:data(sensor:getValue("temperature"))
    end
end)

Obsługę odczytu wielu rejestrów można dodać analogicznie do poprzednich przykładów:

local slaveId  = 1
modbus_slave:onInputRegisterRead(slaveId, function (req, res)
    local firstId = req:address()
    local data = {}
    for id = firstId, firstId + req:size() - 1 do
        local ok, sensor = pcall(function ()
            local device = wtp[id]
            assert(device:getValue("type") == "temperature_sensor")
            return device
        end)
        if not ok then
            res:exception(0x2) -- ILLEGAL DATA ADDRESS
            return
        else
            table.insert(data, sensor:getValue("temperature"))
        end
    end
    res:data(data)
end)

Sterowanie regulatorami temperatury

Zadaną temperaturę, będącą ustawieniem regulatorów, można odczytywać i zmieniać, dlatego odpowiednie będą rejestry typu holding. Odczyt można obsłużyć podobnie jak w przypadku rejestrów typu input:

local slaveId  = 1
modbus_slave:onHoldingRegisterRead(slaveId, function (req, res)
    local regulator = wtp[req:address()]
    res:data(regulator:getValue("target_temperature"))
end)

Obsługę zapisu można dodać następująco:

local slaveId  = 1

modbus_slave:onHoldingRegisterRead(slaveId, function (req, res)
    local regulator = wtp[req:address()]
    res:data(regulator:getValue("target_temperature"))
end)

modbus_slave:onHoldingRegisterWrite(slaveId, function (req, res)
    local regulator = wtp[req:address()]
    regulator:setValue("target_temperature", req:data())
    res:success()
end)

Metoda setValue zgłosi błąd, gdy wartość będzie niepoprawna (np. wykraczająca poza dozwolony zakres). Można to obsłużyć, stosując konstrukcję pcall, która pozwala bezpiecznie wywołać funkcję i złapać ewentualny błąd. Przykład:

...

modbus_slave:onHoldingRegisterWrite(slaveId, function (req, res)
    local addrOk, regulator = pcall(function ()
        local device = wtp[req:address()]
        assert(device:getValue("type") == "temperature_regulator")
        return device
    end)
    if not addrOk then
        res:exception(0x2) -- ILLEGAL DATA ADDRESS
        return
    end

    local setpointOk = pcall(function ()
        regulator:setValue("target_temperature", req:data())
    end)
    if setpointOk then
        res:success()
    else
        res:exception(0x3) -- ILLEGAL DATA VALUE
    end
end)

Podobnie jak przy odczytach, można dodać obsługę zapisu wielu rejestrów jednocześnie, iterując po wartościach i ustawiając je kolejno w odpowiednich regulatorach. Przykładowy kod może wyglądać tak:

...

modbus_slave:onHoldingRegisterWrite(slaveId, function (req, res)
    local firstId = req:address()
    local setpoints = req:data()
    if req:size() == 1 then
        setpoints = { setpoints }
    end

    for id = firstId, firstId + req:size() - 1 do
        local addrOk, regulator = pcall(function ()
            local device = wtp[id]
            assert(device:getValue("type") == "temperature_regulator")
            return device
        end)
        if not addrOk then
            res:exception(0x2) -- ILLEGAL DATA ADDRESS
            return
        end

        local setpointOk = pcall(function ()
            regulator:setValue("target_temperature", req:data())
        end)
        if setpointOk then
            res:success()
        else
            res:exception(0x3) -- ILLEGAL DATA VALUE
            return
        end
    end
end)