Ir para o conteúdo

Comunicação entre autómatos e computadores

Uma parte integrante de um sistema de automação (tal como qualquer equipamento do nosso dia-a-dia), é o diálogo homem-máquina.

Botoneiras e sinalizadores

Um simples botão para ligar o equipamento tem que dizer On/Off ou O/I ou Ligar/Desligar, caso contrário torna-se muito difícil operar.

À medida que a complexidade do sistema de automação vai aumentando, aumenta o número de botões de comando e as sinalizações para informação do que se está a passar com o equipamento:

O uso de botoneiras e sinalizadores têm as suas vantagens: são mais baratas (unitariamente), mais robustas ao manuseamento e de fácil manutenção. No entanto, o comando gerado por uma botoneira ou para um sinalizador está limitado a Sim/Não (podendo uma botoneira ter várias posições, mas sempre limitadas).

Consolas

Uma alternativa ou complemento ao interface de botoneiras e sinalizadores são as consolas de comando e/ou tácteis. Vantagens:

  • São mais compactas, ocupando menos espaço físico,
  • Têm a capacidade de guardar histórico de eventos e/ou alarmes
  • Apresentar uma sinalização mais completa por exemplo: numa avaria podem dizer o que está avariado e ter uma indicação das possíveis causas, um sinalizador apenas sinaliza o que está avariado.
  • Permitem ordens mais complexas, um botão “diz” apenas liga ou não liga (desliga), numa consola podemos dizer liga a X%, por exemplo.
  • Depois de adquirida uma consola, o custo de mais um botão ou sinalização virtual dentro da consola, é nulo.
  • Dão um ar mais profissional à coisa, principalmente quando são policromáticas.

Estes equipamentos têm, no entanto, algumas desvantagens a ter em conta:

  • São desenvolvidos pelo fabricante para comunicar com alguns tipos de autómatos, não todos.
  • Têm que ser programadas com um software específico do fabricante, à excepção da PanelView Component da Allen-Bradley que são programadas com um browser da internet.
  • Capacidade de memória limitada.
  • O seu custo, quando comparado com um botão, é imenso, quando comparado com um conjunto de botões e sinalizadores, pode ser inferior. Quando comparado com um computador, pelo preço de uma mini-consola compramos um computador standard.

Computador

Da mesma maneira, que há aplicações em que é necessário o uso de consolas porque o uso de botoneiras e sinalizadores não é suficiente, há aplicações que exigem o uso de computadores, por exemplo:

  • Grande complexidade do sistema
  • Rastreabilidade
  • DataLog ou registos
  • Relatórios automáticos
  • Envio de dados para a gestão da empresa

Por outro lado, como:

  • O custo do computador é relativamente baixo (quando comparado com uma consola mediana).
  • As capacidades, potencialidades e possibilidade de expansão de um computador são muito superiores.
  • A capacidade de armazenamento é ilimitada.
  • O custo de reparação é baixo.

Tornam atractivo o uso do computador, num sistema de automação, na vez de uma consola.

Introdução

Neste artigo, vamos debater exclusivamente o uso do computador como software de visualização ou SCADA num sistema de automação. Vamos falar sobre:

  • Software dos fabricantes vs software “caseiros”,
  • Exemplos concretos de software “caseiros” a funcionar
  • Vantagens, desvantagens, limitações
  • Alguns cuidados a ter em conta

Softwares dos fabricantes

Uma hipótese para fazermos um programa de visualização, é usarmos um programa específico de desenvolvimento do fabricante do autómato que estamos ou vamos utilizar.

Teremos que adquirir um software de desenvolvimento e em cada aplicação teremos que adquirir e fornecer ao cliente um software de runtime.

Vantagens

  • Garantia de comunicação com o nosso autómato.
  • Integração directa (sem engenharia) das variáveis do autómato, no nosso programa (há excepções a nível de fabricantes e/ou gama de autómatos).
  • Software do fabricante desenvolvido para desenho gráfico.
  • De fácil implementação.
  • Existência de alguns templates.
  • Suporte técnico especializado.
  • Prestígio da marca.

Desvantagens

Custo do software de runtime

O custo do software de runtime virgem a fornecer ao cliente é tanto maior quanto maior for a instalação.

O custo do software, por norma, depende do número de tags. Uma tag é uma variável de comunicação autómato-computador, um bit (On/off), um byte (8 bits) ou uma string (conjunto de N bytes) é considerado uma tag.

Estes programas são vendidos para trabalhar com pacotes de tags, por exemplo 128, 512 ou 2048 tags.

Há softwares, por exemplo o RSViewSE da Allen-Bradley, que em vez de contabilizar tags, contabiliza número de ecrãs: 25, 100 ou 250 ecrã, neste caso o nº de tags por ecrã é ilimitado, mas o princípio é o mesmo, quanto mais ecrãs mais caro o programa.

Software vendido “às peças”

Por norma os softwares base dos fabricantes, apenas permitem comunicar com o autómato e fazer representação gráfica.

As opções, também são pagas. Por exemplo no WinCC Flexible da Siemens se quisermos gravar e/ou ler ficheiros/arquivos do computador temos que comprar a respectiva opção, se quisermos ter receitas, temos que comprar outra opção.

Software limitado

Com o software dos fabricantes, a nossa imaginação (ou do cliente) não é o limite, estamos limitados às possibilidades disponibilizadas pelo fabricante.

VB limitado

Por norma, os softwares permitem o uso de VB ou VBA, mas a sua utilização é limitada, temos que fazer algumas funções simples à mão, por exemplo arredondar um valor.

O seu uso intenso em alguns softwares fazem-no tornar extremamente pesado e lento.

Ficamos eternamente presos ao software

A partir do momento em que desenvolvemos um projecto num software de um fabricante, não é possível migrar ou converter para outro software (excepto claro do mesmo fabricante, com as devidas alterações e correcções).

Compatibilidade entre sistemas operativos limitada ou inexistente

Por norma, os softwares dos fabricantes são desenvolvidos para o Windows e não são compatíveis com outros sistemas operativos.

E mesmo dentro dos sistemas Windows, a sua migração do XP para o Vista, demorou alguns anos a ser concretizada.

Softwares “Caseiros”

Ao referir software caseiro, não estou minimamente a menosprezar ou a dar menos qualidade ao software em si, estou apenas a distingui-lo dos softwares desenvolvidos pelos fabricantes dos autómatos.

Há duas maneiras de fazer um software caseiro:

  • Usando um OPC Server
  • Desenvolvendo comunicação directa com o autómato

OPC Server

Um OPC Server é um programa cuja finalidade é estabelecer a comunicação entre o autómato e computador.

Ao adquirirmos um OPC Server também temos a garantia de comunicação autómato-computador.

No nosso programa, temos que implementar um OPC Client.

Para receber ou enviar dados do ou para o autómato, usamos o OPC Cliente para comunicar com o OPC Server para este comunicar com o autómato.

Vantagens:

  • O custo de um OPC Server é (muito) inferior ao custo de um Runtime.
  • Há mais concorrentes a fazer e desenvolver OPC Server.
  • Do lado da comunicação OPC Server-Autómato, só varia o tipo ou marca de OPC Server, o OPC Client e o nosso software são sempre os mesmos.
  • Depois de desenvolvido o OPC Client é fácil a sua adaptação e expansão.
  • Não está limitado em número de tags ou ecrãs.
  • Por norma, cada OPC Server permite a ligação de vários OPC Clients.
  • É multi-plataforma.
  • Pode facilmente ser usado um OPC Server de outro fabricante, para ser OPC Server tem que ser certificado pela organização OPC, garantindo assim a sua função.

Desvantagens:

  • É necessário um técnico mais especializado para desenvolver o software de visualização e configurar o OPC Server.
  • Suporte técnico do fabricante do autómato mais limitado.
  • Não há integração das variáveis de comunicação com o projecto de automação, têm que ser linkadas à mão.
  • Software feito à medida em linguagem não standard industrial.
  • Por norma, há dois tipos de licenças: para comunicar só com 1 autómato (licença mais barata), para comunicar com vários autómatos (licença mais cara).

Exemplo

Escolha do tipo de OPC

Há vários tipos de OPC Server, o que nos interessa, nesta fase, é o OPC Server DA.

Este OPC Server para cada tag ou item de comunicação disponibiliza 3 variáveis:

  • Value – o valor actual ou o último valor recebido.
  • Quality – representa a qualidade de comunicação desta variável, uma má qualidade, quer dizer que não está a comunicar.
  • TimeStamp – Data e Hora do último valor recebido.

Os itens estão (opcionalmente) agrupados em Groups, permitindo intervalos de update diferentes.

Configuração dos OPC

O primeiro passo que temos que fazer é escolher, adquirir, instalar e configurar o OPC Server.

A configuração do OPC Server varia de fabricante para fabricante, é recomendável a leitura do quick-starter do fabricante.

Teste de comunicação do OPC

Depois de configurar o OPC Server é necessário testá-lo.

Para isso o fabricante fornece no pacote um OPC Client.

Caso não forneça, há vários OPC Cliente grátis na internet.

Desenvolvimento da aplicação
' IMPORTANTE -> Não esquecer de incluir na Reference -> Interop.OPCAutomation.dll do OPC Server instalado

' Módulo relativo às configurações e comunicações OPC
Module mdlOPC
    ' Estrutura de um item de comunicação com o OPC Server
    Structure stuItem
        Dim Value As Object ' Valor actual ou último valor recebido
        Dim TimeStamp As Date ' Data e Hora do último valor recebido
        Dim Quality As Short ' Qualidade da comunicação com este item
    End Structure
    ' Estrutura das variáveis que vamos comunicar com o autómato
    Structure stuValoresAutómato
        Dim PrimeiroValor As stuItem
        Dim SegundoValor As stuItem
        ' criar as restantes variáveis
    End Structure

    ' Dados de comunicação com o autómato, o que realmente vamos tratar no resto do programa
    Dim ValoresAutómato As stuValoresAutómato

    ' Dados relativos ao servidor OPC 
    Dim WithEvents OPCMyServer As OPCAutomation.OPCServer
    Dim WithEvents OPCMyGroups As OPCAutomation.OPCGroups

    Dim WithEvents OPCMyGroup As OPCAutomation.OPCGroup ' Nome do meu grupo de itens (Podemos criar mais grupos com tempos de actualização diferentes)
    Dim OPCMyGroupItems As OPCAutomation.OPCItems ' Os itens do meu grupo

    Friend OPCServerConnected As Boolean = False ' Estado de ligado/desligado ao OPC Server
    Friend OPCServerConnectError As Boolean = False ' Erro de ligação ao OPC Server
    Friend OPCServerUpdate As Boolean = True ' Actualiza ou não os valores

    Friend Const NúmeroItemsMyGroup As Integer = 10 ' Número de itens dentro do meu grupo
    Dim sItemNameMyGroup(NúmeroItemsMyGroup) As String ' Nome/endereço do meu item
    Dim cHMyGroup(NúmeroItemsMyGroup) As Integer
    Dim sHMyGroup(NúmeroItemsMyGroup) As Integer
    Friend oValMyGroup(NúmeroItemsMyGroup) As Object
    Friend dTimeMyGroup(NúmeroItemsMyGroup) As Date
    Friend wQualityMyGroup(NúmeroItemsMyGroup) As Short
    Friend sItemIDsMyGroup(NúmeroItemsMyGroup) As String


    Friend Sub LigaçãoOPC()
        Try
            ' Liga se estiver desligado 
            ' Desliga se estiver ligado
            If OPCServerConnected = False Then
                ' O Servidor não está ligado
                '--- connect OPCServer
                OPCMyServer = New OPCAutomation.OPCServer
                OPCMyServer.Connect("CimQuestInc.IGOPCAB.1", "") ' Mudar o nome para ligar a outro OPC Server, por exemplo para RSLinx da Allen-Bradley
                OPCMyGroups = OPCMyServer.OPCGroups

                ' Cria os item do meu grupo e liga-os ao servidor, se o resultado for False, então houve um erro na ligação
                OPCServerConnectError = Not CriaItemsMyGroup()
                ' Caso haja mais grupos criados, fazer o mesmo (que a linha de cima) para os outros grupos

                OPCServerConnected = True ' Fez uma ligação, pelo que o estado é verdadeiro mesmo que esteja em erro
                OPCServerUpdate = True ' Activa a actualização dos dados

                ' Caso tenha ligado em erro, 
                If OPCServerConnectError = True Then
                    Dim Respostas As Integer
                    Respostas = MsgBox("Erro na Ligação ao OPC", MsgBoxStyle.AbortRetryIgnore)
                    If Respostas = vbCancel Then
                        LigaçãoOPC() ' Como está ligado, desliga
                    ElseIf Respostas = vbRetry Then
                        LigaçãoOPC() ' Como está ligado, desliga
                        LigaçãoOPC() ' volta a ligar
                    End If
                End If

                ' Por causa da leitura assincrona, se não tiver estas linhas dá erro ### não sei porquê
                OPCMyGroup.IsActive = Not OPCMyGroup.IsActive
                OPCMyGroup.IsSubscribed = OPCMyGroup.IsActive
            Else
                ' Já está ligado, por isso desligamos
                '--- disconnect OPCServer

                OPCMyGroup.IsActive = False ' Desactiva o grupo
                OPCMyGroups.Remove(OPCMyGroup.ServerHandle) ' retira os itens do grupo

                OPCMyGroupItems = Nothing
                OPCMyGroups = Nothing
                OPCMyGroup = Nothing

                OPCMyServer.Disconnect()
                OPCMyServer = Nothing
                System.GC.Collect() 'Execute Garbage Collection compulsorily.

                OPCServerConnected = False
            End If
        Catch ex As Exception
            MsgBox("Erro #1 -> mdlOPC -> " & ex.Message)
            OPCServerConnectError = True
        End Try
    End Sub

    Private Function CriaItemsMyGroup()
        ' Cria os itens do meu grupo e estabelece a comunicação
        Dim Resultado As Boolean = True
        Try
            Dim i As Integer
            Dim ServerHandles As Array
            Dim Errors As Array

            ' Configura o grupo
            OPCMyGroup = OPCMyGroups.Add("MyGroup") ' Nome do meu grupo
            OPCMyGroup.UpdateRate = 1000 ' Actualiza do grupo a cada X milisegundos
            OPCMyGroup.IsActive = False
            OPCMyGroup.IsSubscribed = OPCMyGroup.IsActive
            OPCMyGroupItems = OPCMyGroup.OPCItems

            ' Configura cada item do grupo
            i = 0
            i += 1
            sItemIDsMyGroup(i) = "CPU.Alarms.ESD_fault" ' Endereço da 1ª variável de comunicação do grupo, neste caso estou a usar a mnemónica configurada no OPC da Ingear
            cHMyGroup(i) = i
            i += 1
            sItemIDsMyGroup(i) = "Device.N7:0" ' Endereço da 2ª variável de comunicação do grupo, neste caso estou a usar o endereçamento directo num autómato da Allen-Bradley de nome CPU
            cHMyGroup(i) = i
            '... até ao fim dos itens deste grupo

            ' Adiciona os itens ao grupo
            OPCMyGroupItems.AddItems(NúmeroItemsMyGroup, sItemIDsMyGroup, cHMyGroup, ServerHandles, Errors) ', RequestedDataTypes, AccessPaths)

            ' Testa o sucesso e transfere ho Handle
            For i = 1 To NúmeroItemsMyGroup
                If Errors(i) = 0 Then
                    sHMyGroup(i) = ServerHandles(i)
                Else
                    MsgBox("Erro no Item: (" & i.ToString & ") - " & sItemIDsMyGroup(i))
                    Resultado = False
                End If
            Next
        Catch ex As Exception
            MsgBox("Erro #2 -> mdlOPC -> " & ex.Message)
            Resultado = False
        End Try
        Return Resultado
    End Function

    Friend Sub PLC2MyGroup()
        ' Executar esta rotina periodicamente para ler os valores deste grupo
        ' Podemos ler os valores sincronamente ou assincronamente
        ' Vamos usar a leitura assincrona
        ' Ou seja, mandamos ler o grupo e continuamos com o nosso trabalho
        ' Quando a leitura de todos os itens acabar, chama a rotina respectiva
        ' A vantagem deste método nota-se principalmente nas comunicações RS232 de muitos itens.
        ' nesse caso na leitura sincrona, o nosso programa deixa de responder neste momento até à conclusão da comunicação
        Try
            ' Como esta rotina é executada periodicamente e o tempo, até recebermos os dados, pode variar, não queremos chamar a rotina 2 vezes sem receber os dados, por isso
            ' se ainda não fizemos stop ao timer que chama esta rotina, devemos fazer agora
            If OPCServerConnected = True And OPCServerUpdate = True Then
                ' Se estivermos ligado ao OPC Server estado de actualização é verdadeiro
                Static Dim wTransID As Integer = 1100 ' Para cada grupo diferente devemos ter um valor diferente
                Dim wCancelID As Integer
                Dim Errors As Array

                wTransID = wTransID + 1
                OPCMyGroup.AsyncRead(NúmeroItemsMyGroup, sHMyGroup, Errors, wTransID, wCancelID) ' Dá início para leitura assincrona
                Exit Sub
            Else
                ' Como não está ligado ou não está a actualizar os valores, pomos a qualidade a 0 ou seja má qualidade
                Dim i%
                For i = 1 To NúmeroItemsMyGroup
                    wQualityMyGroup(i) = 0
                Next i
            End If
        Catch ex As Exception
            MsgBox("Erro #3 -> mdlOPC -> " & ex.Message)
            Exit Sub
        End Try
    End Sub

    Private Sub OPCMyGroup_AsyncReadComplete(ByVal TransactionID As Integer, ByVal NumItems As Integer, ByRef ClientHandles As System.Array, ByRef ItemValues As System.Array, ByRef Qualities As System.Array, ByRef TimeStamps As System.Array, ByRef Errors As System.Array) Handles OPCMyGroup.AsyncReadComplete
        ' Esta rotina é chamada quando acaba de receber os dados pedidos na rotina acima
        Try
            For i As Integer = 1 To NumItems
                oValMyGroup(ClientHandles(i)) = ItemValues(i)
                dTimeMyGroup(ClientHandles(i)) = TimeStamps(i)
                wQualityMyGroup(ClientHandles(i)) = Qualities(i)
            Next i

            ' Passa os valores recebidos para as nossas variáveis
            Dim j As Integer = 1
            ValoresAutómato.PrimeiroValor.Value = oValMyGroup(j)
            ValoresAutómato.PrimeiroValor.Quality = wQualityMyGroup(j)
            j += 1
            ValoresAutómato.SegundoValor.Value = oValMyGroup(j)
            ValoresAutómato.SegundoValor.Quality = wQualityMyGroup(j)
            ' Fazer o mesmo para os restantes valores

            ' A partir deste ponto já temos os valores deste grupo
            ' Já podemos tratar os valores, representar os resultados, etc...
            ' Podemos também dar start ao timer de actualização deste grupo
        Catch ex As Exception
            MsgBox("Erro #4 -> mdlOPC -> " & ex.Message)
        End Try
    End Sub
    Private Sub MyGroup2PLC(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Try
            ' Da mesma maneira que lemos assincronamente, devemos escrever assincronamente
            ' Devemos ter os mesmos cuidados a escrever que tivemos a ler, por exemplo: o temporizador
            Static Dim wTransID As Integer = 2000
            Dim wCancelID As Integer
            Dim Errors As Array

            Dim i As Integer = 1
            oValMyGroup(i) = ValoresAutómato.PrimeiroValor.Value
            i += 1
            oValMyGroup(i) = ValoresAutómato.SegundoValor.Value
            ' incrementar o i e fazer o mesmo para as restantes variáveis

            wTransID = wTransID + 1
            OPCMyGroup.AsyncWrite(NúmeroItemsMyGroup, sHMyGroup, oValMyGroup, Errors, wTransID, wCancelID)
        Catch ex As Exception
            MsgBox("Erro #5 -> mdlOPC -> " & ex.Message)
        End Try
    End Sub
    Private Sub OPCMyGroup_AsyncWriteComplete(ByVal TransactionID As Integer, ByVal NumItems As Integer, ByRef ClientHandles As System.Array, ByRef Errors As System.Array) Handles OPCMyGroup.AsyncWriteComplete
        Try
            ' Acabou de escrever no autómato, activar novamente o temporizador para a escrita
        Catch ex As Exception
            MsgBox("Erro #6 -> mdlOPC -> " & ex.Message)
        End Try
    End Sub
End Module

Comunicação directa com o autómato

A comunicação directa com o autómato varia naturalmente com o tipo de rede que vamos estabelecer com o autómato e varia de marca para marca.

Vantagens

  • O custo de aquisição do software para o runtime é nulo.
  • É multi-plataforma
  • Com um software pode comunicar com vários autómatos sem custos extras

Desvantagens

  • Estamos dependentes de um suporte técnico duvidoso.
  • Não podemos aproveitar a parte de comunicação entre fabricantes de autómato mesmo que a aplicação seja a mesma.
  • Quando usamos livrarias pré-compiladas, não temos a garantia de continuidade do projecto, por parte de quem o desenvolveu.

Exemplo Autómatos Siemens S7

Para os autómatos Siemens S7 vamos usar uma livraria pré-compilada libnodave.

Pode ser descarregada em http://libnodave.sourceforge.net/

Permite comunicar com S7-200 e S7-300/400 em PPI, MPI ou Ethernet.

Permite usar as principais línguas de programação e inclusive VBA a partir, por exemplo, do Excel.

Além da simples comunicação com o autómato, permite-nos dar ordem de Stop ou Run e fazer download de programa na globalidade ou parcialmente.

Apesar de vir acompanhada com exemplos, source-code e documentação, a sua implementação no nosso projecto pode não ser simples.

Vem acompanhada com ficheiros .exe que servem para teste de comunicação com o autómato.

Nota: Apesar de toda a informação disponibilizada por libnodave, não é fácil começar a comunicar com o autómato.

Um bom auxiliar, à informação disponibilizada, é o ficheiro Modul12.bas do exemplo em Excel.

' IMPORTANTE -> Não esquecer de incluir em References -> libnodave.net.dll do libnodave

' Módulo relativo às configurações e comunicações com a livraria libnodave
' Com S7-200 via porta Série
' Com S7-200 via ethernet CP243
Module mdlLibnodave
    ' Como num exemplo vamos tratar de 2 tipos de comunicação...
    Enum Comunicação
        Série
        Ethernet
    End Enum

    Private Sub Comms()
        Dim TipoComunicação As Comunicação

        Dim localMPI As Integer = 0 ' Configuração dos endereços PPI/MPI do computador,por norma é o Zero
        Dim plcMPI As Integer = 2 ' Configuração dos endereços PPI/MPI do autómato, por norma é o Dois, caso deseje comunicar com mais autómatos, crie mais variáveis com os endereços de cada um
        Dim IP_Adress As String = "192.168.1.30" ' Configuração do IP da carta CP243

        Dim fds As libnodave.daveOSserialType
        Dim di As libnodave.daveInterface
        Dim dc As libnodave.daveConnection
        Dim res As Integer
        Dim buf(1000) As Byte
        Dim inputsres As Integer
        Dim InputBuffer(1000) As Byte ' Buffer para lermos/escrevermos as entradas
        Dim Inputs(6 * 8) As Integer ' Entradas do autómato -> 6 bytes IB0.. IB5 
        Dim outputsres As Integer
        Dim OutputBuffer(1000) As Byte ' Buffer para lermos/escrevermos as saídas
        Dim Outputs(6 * 8) As Integer ' Saídas do autómato -> 6 bytes QB0.. QB5
        Dim memoryres As Integer
        Dim MemoryBuffer(1000) As Byte ' Buffer para lermos/escrevermos as memórias M
        Dim Memory(15 * 8) As Integer ' Memórias M do autómato -> 15 bytes MB0.. MB14
        Dim XMTBuffer(100) As Byte ' Buffer de dados a ser transmitidos para o autómato

        If TipoComunicação = Comunicação.Série Then
            ' Abrimos a comunicação com a porta para leitura
            fds.rfd = libnodave.setPort("com1", "19200", AscW("E")) ' Porta=COM1, BaudRate=19200, Paridade= Par
        ElseIf TipoComunicação = Comunicação.Ethernet Then
            ' Abrimos um socket para a comunicação
            fds.rfd = libnodave.openSocket(102, IP_Adress)
        Else
            fds.rfd = -10000 ' geramos um erro nosso
        End If

        ' Abrimos a comunicação com a porta para escrever igual à que usamos para ler
        fds.wfd = fds.rfd

        If fds.rfd > 0 Then
            ' Se conseguimos abrir a comunicação com a porta
            ' Criamos um interface novo
            If TipoComunicação = TipoComunicação.Série Then
                di = New libnodave.daveInterface(fds, "My Interface 1", localMPI, libnodave.daveProtoPPI, libnodave.daveSpeed19k) ' Importante, caso não estejamos a usar PPI, devemos alterar o daveProtoPPI, e caso o baudrate seja diferente, também devemos alterar
            ElseIf TipoComunicação = TipoComunicação.Ethernet Then
                di = New libnodave.daveInterface(fds, "IF1", localMPI, libnodave.daveProtoISOTCP243, libnodave.daveSpeed187k)
            End If
            di.setTimeout(100000)  ' Caso receba muitos erros -1025 devemos aumentar este valor
            res = di.initAdapter ' Iniciamos o Adaptador

            If res = 0 Then
                ' Se o resultado de res é ZERO, então não há erro, podemos continuar
                dc = New libnodave.daveConnection(di, plcMPI, 0, 2)  ' criamos uma nova ligação, Nota: caso MPI a rack e slot não são importantes
                res = dc.connectPLC() ' Ligamos ao autómato
                If res = 0 Then
                    ' Caso o resultado de res é ZERO, então não há erro, podemos continuar
                    inputsres = dc.readBytes(libnodave.daveInputs, 0, 0, 6, InputBuffer) ' Mandamos ler as entradas
                    outputsres = dc.readBytes(libnodave.daveOutputs, 0, 0, 6, OutputBuffer) ' Mandamos ler as saídas
                    memoryres = dc.readBytes(libnodave.daveFlags, 0, 0, 16, MemoryBuffer) ' Mandamos ler as memórias
                    ' Cada vez que mandamos ler, perdemos tempo/performance
                    ' É preferível ler muitos dados e usar só metade, do que ler uma parte, depois outra, depois...
                    ' Podemos ler Timers, Counters, Memórias V, bancos de memória DB, etc...

                    If inputsres = 0 And outputsres = 0 And memoryres = 0 Then
                        ' Caso o resultado de res é ZERO, então não há erro, podemos continuar
                        Dim h As Integer = 0
                        ' Neste exemplo recebemos bytes, mas queremos bits, então é necessário converter 
                        ' No caso de lermos valores reais ou floats, há uma função específica para fazer a conversão
                        For i As Integer = 0 To 5
                            For j As Integer = i * 8 To (i * 8 + 7)
                                Inputs(j) = InputBuffer(i) And (2 ^ h)
                                Outputs(j) = OutputBuffer(i) And (2 ^ h)
                                Memory(j) = MemoryBuffer(i) And (2 ^ h)
                                If Inputs(j) > 1 Then Inputs(j) = 1
                                If Outputs(j) > 1 Then Outputs(j) = 1
                                If Memory(j) > 1 Then Memory(j) = 1
                                h += 1
                            Next
                            h = 0
                        Next
                        ' Neste momento já temos os dados todos que lemos do autómato, já podemos tratar e representar no programa

                        If Inputs(0) = 0 Then
                            ' fazer código para ZERO
                        Else
                            ' fazer código para UM
                        End If
                        ' Fazer o mesmo ou equivalente para os restantes valores
                        ' Não esquecer que agora os bits estão todos seguidos e não agrupados em bytes, ou seja:
                        ' I0.0 corresponde a Inputs(0), I1.0 corresponde a Inputs(8), I2.0 correspondente a Inputs(16), etc...

                        ' Organizar os valores e metê-los no array XMTBuffer para serem enviados para o autómato

                        ' Escreve nas memórias V
                        If dc.writeBytes(libnodave.daveDB, 1, 1020, 16, XMTBuffer) <> 0 Then
                            ' Se o resultado for diferente de ZERO, então houve um erro
                            MsgBox(TimeOfDay & " - Erro na escrita de Ordens VB1020 -> " & res & vbCrLf) ' " <> " & libnodave.daveStrerror(res) & vbCrLf)
                        End If
                    Else
                        ' Erro ao ler os valores
                        MsgBox("Error {0:d}={1:s} in readBytes.res = " & res & vbCrLf)
                    End If
                    dc.disconnectPLC() ' Desligamo-nos do autómato
                Else
                    ' Erro de ligação ao autómato
                    MsgBox("Error {0:d}={1:s} in connectPLC.res = " & res & vbCrLf)
                End If
                di.disconnectAdapter()  ' Desligamos o adapter
            Else
                ' Erro no adapter
                MsgBox("Error {0:d}={1:s} in initAdapter.res = " & res & vbCrLf)
            End If
            ' Fechamos a porta
            libnodave.closePort(fds.rfd)    ' Clean up
        Else
            ' Erro ao abrir a porta
            MsgBox("Erro ao abrir a porta de comunicação!" & vbCrLf)
        End If
    End Sub
End Module

Exemplo em Modbus/TCP Over Ethernet

O Modbus também é uma rede de comunicação amplamente usada.

O exemplo que apresentámos foi desenvolvido para receber dados de um analisador de energia SENTRON PAC3200 da Siemens em Modbus/TCP Over Ethernet.

Nota: Neste projecto não estava contemplado o envio de dados para o analisador, apenas a leitura de estados. A adaptação para escrever dados não é complexa, só não foi executada para este exemplo por falta de equipamentos de teste, ou seja, impossibilidade de garantir o seu funcionamento.

' Módulo relativo às configurações e comunicações em Modbus Over Ethernet

Imports System.Net
Imports System.Net.Sockets

Module mdlModbus
    Private ClientSocket As Socket
    Dim MbusQuery(11) As Byte ' Telegrama que vamos enviar para a comunicação

    Private Sub LerDados() ' Cria o Socket, liga ao socket e aponta para a rotina de fim de ligação 
        Try
            Dim Addr As IPAddress = Dns.GetHostEntry("192.168.1.30").AddressList(0)

            If Not Addr Is Nothing Then
                ' Cria um Endpoint IP 
                Dim EP As New IPEndPoint(Addr, 502)
                ' Cria um novo Socket
                ClientSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
                ' Manda ligar ao endereço IP
                ClientSocket.BeginConnect(EP, AddressOf ConnectCallBack, Nothing) ' É uma ligação assincrona, por isso...
            End If
        Catch ex As Exception
            MsgBox("Erro #1 -> mdlModbus -> " & ex.Message)
        End Try
    End Sub
    Private Sub ConnectCallBack(ByVal ar As IAsyncResult)
        ' Esta rotina é chamada assincronamente quando o socket é ligado
        Try
            ClientSocket.EndConnect(ar) ' -- Terminou a ligação

            ' Os dados que queremos ler estão armazenados numa tabela

            ' O endereço e o comprimento dos valores são compostos em Word, mas temos que compor um telegrama em Bytes
            Dim StartLow As Byte ' LSB do endereço que vamos ler
            Dim StartHigh As Byte ' MSB do endereços que vamos ler
            Dim LengthLow As Byte ' LSB do comprimento de dados
            Dim LengthHigh As Byte 'MSB do comprimento de dados

            ' Neste exemplo vamos ler os dados do endereço 801
            ' e os 40 seguidos
            StartLow = 801 Mod 256
            StartHigh = 801 \ 256
            LengthLow = 40 Mod 256  'cada float representa 4 bytes
            LengthHigh = 40 \ 256 'cada float representa 4 bytes

            ' Construção do telegrama a ser enviado
            MbusQuery(0) = 1 \ 256 ' MSB Endereço Modbus do destinatário
            MbusQuery(1) = 1 Mod 256 ' LSB Endereço Modbus do destinatário
            MbusQuery(2) = 0 'Protocol Identifier
            MbusQuery(3) = 0 'Protocol Identifier
            MbusQuery(4) = 0 'Comprimento High
            MbusQuery(5) = 6 'Comprimento Low
            MbusQuery(6) = 1 'ID
            MbusQuery(7) = 3 'Código da Função 0x03 ou 0x04 ### Para mais informações, consultar os catálogos técnicos do Modbus
            MbusQuery(8) = StartHigh
            MbusQuery(9) = StartLow
            MbusQuery(10) = LengthHigh
            MbusQuery(11) = LengthLow
            'MbusQuery = Chr(0) + Chr(0) + Chr(0) + Chr(0) + Chr(0) + Chr(6) + Chr(1) + Chr(3) + Chr(StartHigh) + Chr(StartLow) + Chr(LengthHigh) + Chr(LengthLow)

            ' Envia o Telegrama de pedido de dados
            ClientSocket.Send(MbusQuery, MbusQuery.Length, SocketFlags.None)

            Dim bytes(4095) As Byte ' buffer de bytes recebido (ou a receber)
            ClientSocket.BeginReceive(bytes, 0, bytes.Length, SocketFlags.None, AddressOf ReceiveCallBack, bytes) ' É uma ligação assincrona, por isso...
        Catch ex As Exception
            MsgBox("Erro #2 -> mdlModbus -> " & ex.Message)
        End Try
    End Sub
    Private Sub ReceiveCallBack(ByVal ar As IAsyncResult)
        ' Esta rotina é chamada assincronamente, quando recebemos a resposta ao pedido de dados
        Try
            Dim bytes() As Byte = CType(ar.AsyncState, Byte())
            Dim RemoteEP As String = ClientSocket.RemoteEndPoint.ToString
            Dim numbytes As Int32 = ClientSocket.EndReceive(ar)

            If numbytes = 0 Then
                ' ERRO não recebeu dados
            Else
                ' Caso contrário, fazer rotina de tratamento de dados.
                ' Não esquecer que há um limite de dados enviados por telegrama (205 bytes), ultrapassando esse limite, os dados ficam a zero
                Dim i As Integer
                i = 9 ' Os primeiros bytes são os endereços e comprimentos, consultar catálogo técnico sobre modbus para mais informação
                ' Tratar os dados, não esquecer que os dados vêm organizados em Bytes e é necessário convertê-los para Inteiros, Reais, etc...
                i = i + 4 ' Não esquecer que como recebemos 4 bytes por valor, para tratarmos do próximo valor temos que somar 4 ao índice do array
            End If
            ' Limpa o buffer e fecha a ligação
            ClientSocket.Shutdown(SocketShutdown.Both)
            ClientSocket.Close()
            ' Neste momento, já podemos fazer start ao temporizador para voltar a pedir dados
            ' Não esquecer que quando pedimos dados, devemos parar o temporizador
        Catch ex As Exception
            MsgBox("Erro #3 -> mdlModbus -> " & ex.Message)
        End Try
    End Sub
End Module

Exemplo Autómato Allen-Bradley em DF1

Pode ser descarregada em http://sourceforge.net/projects/abdf1/

Tem uma class em VB.Net 2005, um exemplo completo e documentação.