Software Development / Inżynieria

Dane przemysłowe w Internecie – implementacja REST API z wykorzystaniem sieci polowej MODBUS

Czerwiec 8, 2021 0
Podziel się:

Postępująca transformacja cyfrowa dotyka wiele dziedzin życia oraz gospodarki. Nie omija to również przemysłu poprzez realizację założeń czwartej rewolucji przemysłowej, opierającej się na głębokiej integracji systemów sterowania oraz nadzoru z zaawansowanymi systemami informatycznymi. Mają one za zadanie podnosić wskaźniki wydajności, jakości oraz bezpieczeństwa prowadzonych procesów produkcyjnych. Pomimo sporych inwestycji przeznaczanych przez przedsiębiorstwa na modernizację aparatury sterująco-kontrolnej w obiegu nadal funkcjonują urządzenia nieprzystosowane do funkcjonowania w aktualnych systemach zarządzania obiektem sterowania. W związku z tym, częstym problemem podczas realizacji idei przemysłu 4.0 jest również starzejąca się infrastruktura sieciowa. Istnieje natomiast możliwość obniżenia kosztów takiej transformacji poprzez wykorzystanie zewnętrznych bibliotek wraz z REST API. W niniejszym przykładzie opisana zostanie implementacja REST API z wykorzystaniem sieci polowej MODBUS TCP. W przykładzie wykorzystano środowisko CoDeSys oraz REST API napisane z wykorzystaniem .NET WEB API w języku C#.

MODBUS TCP jest wersją otwartego protokołu MODBUS wykorzystującego ethernet jako medium komunikacyjne zarówno przewodowo jak i bezprzewodowo. Wykorzystuje on w komunikacji port 502. Działa on w architekturze klient-serwer (master-slave). W przeciwieństwie do MODBUS RTU daje to przewagę w możliwości zawarcia wielu klientów, aniżeli jednego w zakresie sieci.

PLC + MODBUS

Schemat obiektu wygląda następująco:

Modbus REST API Scheme 1 - Dane przemysłowe w Internecie – implementacja REST API z wykorzystaniem sieci polowej MODBUS

Na wstępie przygotowujemy strukturę projektu PLC wraz z przygotowaniem infrastruktury sieciowej dla MODBUS TCP. Device Ethernet konfigurujemy zgodnie z aktualną dla nas siecią Ethernet.

CoDeSys network config - Dane przemysłowe w Internecie – implementacja REST API z wykorzystaniem sieci polowej MODBUS

Kolejnym krokiem jest przygotowanie mapowania zmiennych sieciowych do wykorzystania w programie.

CoDeSys Modbus variables config - Dane przemysłowe w Internecie – implementacja REST API z wykorzystaniem sieci polowej MODBUS

Należy koniecznie pamiętać o ustawieniu odpowiedniego Taska dla działania naszej sieci polowej. W przykładzie jest to główny Task wywoływany cyklicznie.

CoDeSys task set - Dane przemysłowe w Internecie – implementacja REST API z wykorzystaniem sieci polowej MODBUS

Na tym etapie przygotowany projekt PLC jest gotowy do pracy w trybie slave (server), do którego będą przysyłane zapytania o dane.

REST API + NMODBUS

Kolejnym etapem jest przygotowanie REST API w środowisku .NET. W projekcie wykorzystano bibliotekę NModbus, która pozwala na dostęp do urządzeń wspierających sieć polową MODBUS zarówno w wersji RTU jak i TCP. Na wstępie wymagana jest konfiguracja klienta sieci MODBUS – w tym przypadku master’a.


public bool Connect()
{
   if (_client == null)
      return false;
   var retriesTimeSpan = Backoff.LinearBackoff(TimeSpan.FromSeconds(1), 5);
   var policy = Policy.Handle<SocketException>().WaitAndRetry(retriesTimeSpan);
   var connectionResult = policy.ExecuteAndCapture(() => _client.Connect(_hostname, _port));
   if (connectionResult.Outcome == OutcomeType.Failure)
   {
      var validationResult = new TcpClientValidationResult
      {
         HasErrors = true,
         ExceptionType = connectionResult.FinalException.InnerException
      };
   validationResult.ErrorMessage = validationResult.GetErrorMessageByExceptionType();
   ValidationResult = validationResult;
   }
   else
      ValidationResult = new TcpClientValidationResult();
   return _client.Connected;
}

Każdy endpoint wysyła zapytanie do service’ów, które to poprzez Gateway mają dostęp do sieci MODBUS.


[HttpGet("feedMotorSpeed")]
public async Task<IActionResult> GetFeddMotorSpeed()
{
   var serviceResponse = await _packagingCellService.GetFeedMotorSpeed();
   var actionResponse =_actionResponse.GetResponseFromService(serviceResponse);
   return serviceResponse.ValidationResult.HasErrors ? BadRequest(actionResponse) : Ok(actionResponse);
}

public async Task<IServiceResponse<ushort>> GetFeedMotorSpeed()
{
    var targetAddresses = _addressProvider.GetAddress(CompressorPackagingCellAddresses.FEED_MOTOR_SPEED);
    if (HasError(_addressProvider.ValidationResult))
    {
        SetResponseError(_addressProvider.ValidationResult);
        return GetResponse(_packagingCell.Model.FeedMotorSpeed);
    }
    _packagingCell.Model.FeedMotorSpeed = await _modbusGateway.Reader.ReadNumberValue(targetAddresses);
    if (HasError(_modbusGateway.Reader.ValidationResult))
        SetResponseError(_modbusGateway.Reader.ValidationResult);
    
    return GetResponse(_packagingCell.Model.FeedMotorSpeed);
}

Adresy poszczególnych rejestrów sieci MODBUS są przechowywane w stałych. Walidacja następuje przy każdym ich wykorzystaniu.

public int GetAddress(string address)
{
    var addressModel = _addressParser.Parse(address);
    var addressValidationResult = _validator.Validate(addressModel);
    if (!addressValidationResult.HasErrors)
        _address = addressModel.ByteAddress + addressModel.BitAddress;
    
    ValidationResult = addressValidationResult;
 
    return _address;
}

Biblioteka Polly zajmuje się realizacją podejmowania prób ponownego połączenia oraz ponownego odczytu danych z sieci polowej. Dzięki niej łatwo można skonfigurować politykę obsługi wyjątków oraz podejmowania ponownych prób wykonania akcji.

public async Task<ushort> ReadNumberValue(int address)
{
    var response =  0;
    using (var client = _client.GetClient())
    {
        var timeSpan = Backoff.LinearBackoff(TimeSpan.FromSeconds(1), 4);
        var modbusMasterPolicy = Policy.Handle<Exception>().WaitAndRetry(timeSpan);
        var policyExecutionResult = modbusMasterPolicy.ExecuteAndCapture(() => SetModbusMaster(client));
        if(policyExecutionResult.Outcome == OutcomeType.Failure)
        {
            var validationResult = new ModbusReadingValidationResult();
            validationResult.GetFromSlaveException(policyExecutionResult.FinalException, (int)EModbusTransportErrorCode.ConnectionError);
            ValidationResult = validationResult;
            return (ushort)response;
        }
        var modbusReadingPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(timeSpan);
        var modbusReadingPolicyResult =  modbusReadingPolicy.ExecuteAndCaptureAsync(async () =>
            await _modbusMaster.ReadInputRegistersAsync(0, (ushort)address, 1).ConfigureAwait(false));
        if(modbusReadingPolicyResult.Result.Outcome == OutcomeType.Failure)
        {
            var validationResult = new ModbusReadingValidationResult();
            validationResult.GetFromSlaveException(modbusReadingPolicyResult.Result.FinalException,
           			        (int)EModbusTransportErrorCode.ReadingError);
            ValidationResult = validationResult;
            return (ushort)response;
        }
        response = modbusReadingPolicyResult.Result.Result[0];
    }
    return (ushort)response;
}

Następnie przechwycona odpowiedź lub błąd wynikający z niepowodzenia operacji przekazywane są do metody formatującej odpowiedź końcową.

public ServiceResponseBase GetResponseFromService<T>(IServiceResponse<T> serviceResponse)
{
    var responseModel = new ModbusActionResponseModel
    {
        ResponseMessage = serviceResponse.ValidationResult.HasErrors ? 
            SetErrorMessage(serviceResponse.ValidationResult) : 
            SetPayloadMessage(serviceResponse.Payload),
    };
    responseModel.ResponseMessage.ResponseIssued = DateTime.Now;
    return responseModel.ResponseMessage;
}

Te same kroki odpowiadają również ustawianiu rejestrów MODBUS. W tym celu należy wykorzystać inne (podać jakie) metody przygotowane w ramach biblioteki NModbus. Metoda „WriteSingleRegister” wpisuje wartość do całego rejestru – jest nią najczęściej wartość liczbowa. Natomiast metoda „WriteSingleCoil” pozwala na zmianę stanu pojedynczego bitu w rejestrze.

void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value);
void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value);

Jak widać, niewątpliwą zaletą takiego rozwiązania jest jego niski koszt implementacji. O ile sama sieć MODBUS nie jest przystosowana do transmisji dużych pakietów danych (253 bajty), o tyle przy wymianie niewielkich ilości informacji jest w stanie zaspokoić potrzeby monitorowania podstawowych lub wrażliwych danych o obiekcie. Otwiera to drogę do archiwizowania i wizualizowania danych z obiektu oraz możliwość zadawania sygnałów sterujących zdalnie. Możliwości opisanej wyżej konfiguracji mogą również służyć do prototypowania różnych rozwiązań przemysłowych – od niewielkich instalacji sterowania i nadzoru do systemów archiwizacji oraz agregacji i prezentacji danych w systemach informatycznych.  Istotnym elementem jest również zabezpieczanie takiej instalacji przed niepożądanym dostępem. W takim przypadku może on nie tylko narazić obiekt na utratę danych, ale również doprowadzić obiekt do stanu niebezpiecznego.

 

Oceń ten post
Tagi: , ,
Michał Kasperski
Autor: Michał Kasperski
Programuję od 3 lat rozpoczynając swoją przygodę od sterowników przemysłowych. Obecnie pracuję w SII jako software developer w technologii ASP .NET. W wolnym czasie czytam książki historyczne, programuję oraz gram w badmintona.

Imię i nazwisko (wymagane)

Adres email (wymagane)

Temat

Treść wiadomości

Zostaw komentarz