PLC support: Giving access to industrial control systems | Viam

Industries

October 26, 2023

PLC support: Giving access to industrial control systems

Pete Garafano
Consulting Engineer

The world of industrial controls is dominated by large, vertically integrated corporations, expensive hardware, and esoteric programming languages. This creates a large barrier to entry by requiring sizable capital expenditures and specialized software engineers to program these systems. As part of Viam's goal of democratizing access to smart machines, we have added support for the Revolution Pi line of PLCs from KUNBUS to the Viam platform.

The Revolution Pi takes a Raspberry Pi Computer Module and fits it into a DIN-rail mountable form factor. The module runs Linux, allowing you to run a wide array of widely available software, including Viam. When combined with the specially designed I/O modules available from KUNBUS, interacting with the world has never been easier. Instead of having to add relays or logic level shifters to interact with 24v signals, the I/O modules do this for you. They also provide strong guarantees around power on/off states of the I/O points as well as electrical isolation for the compute module.

Adding Viam to your Revolution Pi opens up a world of possibilities for your industrial control project. Not only are you adding the world of Viam to your PLC project, you are adding access to a wide variety of industrial control components to the world of Viam. Now you are free to include things like video, ML Models, and internet data sources in your industrial control project. Oh, and you can program the whole thing in your favorite language, whether that is C++, Python, Go, or any number of other languages Viam supports.

‍ How to use a PLC with Viam

Now that we know what the Revolution Pi is, and have an idea of what it can do, let's use it with Viam, as shown in the simple steps outlined below:

  1. Follow the Quick Start from KUNBUS to ensure you're running the latest software
  2. Configure the connected I/O Modules using the built in PiCtory interface on the Revolution Pi
  3. Install the viam-server
  4. Add the viam-revolution-pi module to your robot

Testing the Viam PLC setup

Now let's test the setup. Open the Control Tab for the Revolution Pi on app.viam.com and expand the revpi Board card. From here you can get current pin states, or even toggle them on/off. Let's check the status of O_1:

Now turn it on by selecting "high," and clicking "Set Pin State."

You can check the pin state with a multimeter to confirm. Congratulations, you have just used Viam to interact with industrial control hardware!

How Viam PLC support works: The code

You might say, "Well that's cool and all, but I'm not going to control my system from this basic UI, it's way too complicated." Well, you would have a point, so let's take a look at how this works from code, specifically using the example of Python.

First, let's head back over to app.viam.com and click the "Code sample" tab to install the viam-sdk in your Python environment. Next, copy the provided sample code, and be sure to click "Include secret" to make sure the viam-sdk can connect to the machine. I went ahead and made some modifications to the sample code to get what I have below:

import asyncio
from viam.robot.client import RobotClient
from viam.rpc.dial import Credentials, DialOptions
from viam.components.sensor import Sensor
from viam.components.board import Board

async def connect():
    creds = Credentials(
        type='robot-location-secret',
        # Replace "<SECRET>" (including brackets) with your robot's secret
        payload='<SECRET>')
    opts = RobotClient.Options(
        refresh_interval=0,
        dial_options=DialOptions(credentials=creds)
    )
    return await RobotClient.at_address('revpi88384-main.nen8isx27t.viam.cloud', opts)

async def main():
    async with await connect() as robot:
        # Get a reference to the board
        revpi = Board.from_robot(robot, "revpi")
        # Get a pin by name
        revpi_pin = await revpi.gpio_pin_by_name("O_1")
        # Get the current state of the pin
        current_state = await revpi_pin.get()
        print(f"Current state: {current_state}")
        # Turn the output on
        await revpi_pin.set(True)
        # Get the state to prove it is on
        current_state = await revpi_pin.get()
        print(f"Current state: {current_state}")
        # Turn the output off
        await revpi_pin.set(False)
        # Get the state to prove it is off
        current_state = await revpi_pin.get()
        print(f"Current state: {current_state}")

if __name__ == '__main__':
    asyncio.run(main())

First, we get a connection to the robot, and then we get a reference to the board named "revpi." From there, we can get a reference to the pin. Once we have this pin, we can begin to interact with it. This is in contrast to traditional PLCs, which typically handle I/O by directly interacting with memory addresses. However, this really isn't all that different; you can think of this as simply asking the system for the right memory address, instead of accessing hard coded values.

So, what does our output look like?

2023-09-27 16:08:03,554   INFO   viam.rpc.dial (dial.py:247)   Connecting to socket: /tmp/proxy-CekengwA.sock
Current state: False
Current state: True
Current state: False

As you can see, we started with the pin off, then we turned it on, and then turned it off again. Our output confirms this.

This is all well and good, but you might remind me that I said you can use multiple languages. Yes, that’s true, I did. So let's take a look at doing this with Go.

package main
import (
  "context"
  "github.com/edaniels/golog"
  "go.viam.com/rdk/components/board"
  "go.viam.com/rdk/robot/client"
  "go.viam.com/rdk/utils"
  "go.viam.com/utils/rpc"
)
func main() {
  ctx := context.Background()
  logger := golog.NewDevelopmentLogger("client")
  robot, err := client.New(
    ctx,
    "revpi88384-main.nen8isx27t.viam.cloud",
    logger,
    client.WithDialOptions(rpc.WithCredentials(rpc.Credentials{
      Type:    utils.CredentialsTypeRobotLocationSecret,
    // Replace "<SECRET>" (including brackets) with your robot's secret
          Payload: "<SECRET>",
    }))
  )
  if err != nil {
    logger.Fatal(err)
  }
  defer robot.Close(ctx)
  logger.Info("Resources:")
  logger.Info(robot.ResourceNames())
  // Note that the pin supplied is a placeholder. Please change this to a valid pin.
  // revpi
  revpiComponent, err := board.FromRobot(robot, "revpi")
  if err != nil {
    logger.Error(err)
    return
  }
  revpi_pin, err := revpiComponent.GPIOPinByName("O_1")
  if err != nil {
    logger.Error(err)
    return
  }
  // Get the current state of the pin
  current_state, err := revpi_pin.Get(ctx, nil)
  if err != nil {
    logger.Error(err)
    return
  }
  logger.Infof("Current state: %v", current_state)
  // Turn the output on
  err = revpi_pin.Set(ctx, true, nil)
  if err != nil {
    logger.Error(err)
    return
  }
  // Get the state to prove it is on
  current_state, err = revpi_pin.Get(ctx, nil)
  if err != nil {
    logger.Error(err)
    return
  }
  logger.Infof("Current state: %v", current_state)
  // Turn the output off
  err = revpi_pin.Set(ctx, false, nil)
  if err != nil {
    logger.Error(err)
    return
  }
  // Get the state to prove it is off
  current_state, err = revpi_pin.Get(ctx, nil)
  if err != nil {
    logger.Error(err)
    return
  }
  logger.Infof("Current state: %v", current_state)
}

As you can see, the Go code is very similar to the Python code. It follows the same process, uses similar method names with some language specific parameters, and provides very similar output to our Python script:

2023-09-27T16:45:50.025-0400   INFO   client  demo/demo.go:30 Resources:
2023-09-27T16:45:50.025-0400   INFO   client  demo/demo.go:31 [rdk:service:sensors/builtin rdk:service:data_manager/builtin rdk:service:motion/builtin rdk:component:board/revpi]
2023-09-27T16:45:50.051-0400   INFO   client  demo/demo.go:52 Current state: false
2023-09-27T16:45:50.076-0400   INFO   client  demo/demo.go:67 Current state: true
2023-09-27T16:45:50.100-0400   INFO   client  demo/demo.go:82 Current state: false

How Viam compute offloading changes the game for ICS

Perhaps you are now thinking to yourself: This is nice! I can hire regular software engineers to program my industrial control systems.

However, I've left one bit of interesting information out from my demos. None of my scripts have been running on my Revolution Pi. They have all been running on my desktop, controlling the I/O of the Revolution Pi, remotely, from a different state.

This is part of the beauty of Viam. You can choose to run everything from a central location, or directly on the Revolution Pi itself.

Why might you want to run your program on something other than the board with the I/O attached? Compute resources can be a big reason. If you need more compute power to run things like ML Models or OpenCV models, you can offload all the processing and commands to a more powerful machine, and not have to worry about trying to message commands to your I/O boards, you can do it straight from the code running the heavy processing.

If this sounds interesting to you, I would highly encourage you to take a deeper look at Viam in 3 Minutes and Viam Security and Compliance.