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](https://revolutionpi.com/) line of **PLCs** from KUNBUS to the Viam platform.

The Revolution Pi takes a [Raspberry Pi Computer Module](https://www.raspberrypi.com/products/compute-module-4/?variant=raspberry-pi-cm4001000) 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](https://docs.viam.com/program/).

## ‍ **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](https://revolutionpi.com/tutorials/quick-start) 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](https://docs.viam.com/installation/#install-viam-server)
4. [Add](https://docs.viam.com/modular-resources/configure/) the [viam-revolution-pi](https://app.viam.com/module/viam-labs/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](https://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](http://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:

```python
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?

```plaintext
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**.

```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:

```plaintext
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](https://docs.viam.com/manage/ml/) 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](https://docs.viam.com/viam/) and [Viam Security and Compliance](/content/product/security/index.html).
