Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d4f92cf
Add initial extension
Apr 18, 2024
b4aeccc
Add configurable AMS Net Id and refresh rate
Apr 18, 2024
9e6a655
Add configurable AMS Net Id and refresh rate
Apr 18, 2024
7ccc8b5
Merge remote-tracking branch 'origin/main'
Apr 18, 2024
895fdad
Update some comments
Apr 18, 2024
916771c
Add pyads dependency
Joshpolansky Apr 22, 2024
6f43c0f
Add todo
Joshpolansky Apr 22, 2024
2269514
Add a launch.json
Joshpolansky Apr 22, 2024
af20e32
Fix data size
Joshpolansky Apr 22, 2024
a5da970
Add setting management
Joshpolansky Apr 23, 2024
4083ab1
Cleanup refresh function
Joshpolansky Apr 23, 2024
1fd4c53
Add support for lists of variables
Joshpolansky Apr 23, 2024
7d6a03a
build out api
Joshpolansky Apr 23, 2024
8d15bf6
Add basic API for apps to use
Joshpolansky Apr 23, 2024
63ce80b
Remove omni from the extension name
Joshpolansky Apr 23, 2024
214ecc6
return empty dict if no variables are given
Joshpolansky Apr 23, 2024
3f4ce52
Fix length check
Joshpolansky Apr 24, 2024
3a964ee
Update todo's
Joshpolansky Apr 24, 2024
dbca51a
Add images and readme
Joshpolansky Apr 24, 2024
1056f15
Add a README with usage instructions
Joshpolansky Apr 24, 2024
e948bb0
Add write_req unsubscribe
Joshpolansky Apr 24, 2024
4299b3f
Improve status message by showing an error for at least 1 second befo…
Joshpolansky Apr 24, 2024
94f584e
Remove event sender ID
Joshpolansky Apr 24, 2024
9c3929a
Update description
Joshpolansky Apr 24, 2024
1ec4eab
Update dev readme
Joshpolansky Apr 24, 2024
2fcc7e9
Add docs to Api
Joshpolansky Apr 24, 2024
a6623a4
Update API name
Joshpolansky Apr 24, 2024
d525710
Add docstring to AdsDriver
Joshpolansky Apr 24, 2024
55ad2da
Update API docs
Joshpolansky Apr 24, 2024
53fe69c
Merge branch 'feature/release_prep'
Joshpolansky Apr 24, 2024
65e9b0e
Fix the readme link
Joshpolansky Apr 24, 2024
365e00b
Apply suggestions from code review
Joshpolansky Apr 25, 2024
8e39865
Add readme and license files
agrayzel May 1, 2024
0999edc
Add file headers
agrayzel May 1, 2024
2a94f44
Add link to pyads
agrayzel May 2, 2024
7447284
Merge pull request #3 from loupeteam/feature/oss-release
agrayzel May 2, 2024
958fdb3
Remove isaac dependencies
May 15, 2024
ab65d60
Update extension menu text
May 16, 2024
c76d647
Add support for detecting when a connection is live
May 16, 2024
130e03d
Update the READMEs
May 16, 2024
21f6f66
Remove remaining references to IsaacSim
May 16, 2024
5b450bb
Update extension configuration
May 16, 2024
611ff97
Update repo structure to adhere to standard format
May 16, 2024
c055b44
Update README.md
AndrewMusser May 16, 2024
9d23a3a
Add intermediate 'simulation' folder to adhere to standard
May 16, 2024
c9d5ee1
Update README
May 16, 2024
bc98ff3
Add a "PLC" keyword
May 16, 2024
78832b2
Merge pull request #4 from loupeteam/release/prep
AndrewMusser May 16, 2024
602a08a
Add tests adapted from our other PLC extension
shanereetz Jul 24, 2024
d84386a
Add [[tests]] to extension TOML
shanereetz Jul 24, 2024
43a4f24
Integrate Dave Wiens' parsing algorithm into this project
shanereetz Jul 24, 2024
4f29128
Update unit tests with new parsing function name
shanereetz Jul 24, 2024
11d6979
Merge pull request #5 from loupeteam/feature/AddParseUnitTest
shanereetz Sep 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"ms-vscode.cpptools",
"ms-python.python",
"ms-python.vscode-pylance",
"bungcip.better-toml"
],
}
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Attach",
"type": "python",
"request": "attach",
"port": 3000,
"host": "127.0.0.1",
"justMyCode": false
},
]
}
342 changes: 342 additions & 0 deletions .vscode/settings.json

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The MIT License

Copyright 2024 Loupe

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Info
This tool is provided by Loupe.
https://loupe.team
info@loupe.team
1-800-240-7042

# Description

This is an extension that connects Beckhoff PLCs into the Omniverse ecosystem. It leverages [pyads](https://github.com/stlehmann/pyads) to set up an ADS client for communicating with PLCs.

# Documentation

Detailed documentation can be found in the extension readme file [here](exts/loupe.simulation.beckhoff_bridge/docs/README.md).

# Licensing

This software contains source code provided by NVIDIA Corporation. This code is subject to the terms of the [NVIDIA Omniverse License Agreement](https://docs.omniverse.nvidia.com/isaacsim/latest/common/NVIDIA_Omniverse_License_Agreement.html). Files are licensed as follows:

### Files created entirely by Loupe ([MIT License](LICENSE)):
* `ads_driver.py`
* `BeckhoffBridge.py`

### Files including Nvidia-generated code and modifications by Loupe (Nvidia Omniverse License Agreement AND MIT License; use must comply to whichever is most restrictive for any attribute):
* `__init__.py`
* `extension.py`
* `global_variables.py`
* `ui_builder.py`

This software is intended for use with NVIDIA Omniverse apps, which are subject to the [NVIDIA Omniverse License Agreement](https://docs.omniverse.nvidia.com/isaacsim/latest/common/NVIDIA_Omniverse_License_Agreement.html) for use and distribution.

This software also relies on [pyads](https://github.com/stlehmann/pyads), which is licensed under the MIT license.
37 changes: 37 additions & 0 deletions exts/loupe.simulation.beckhoff_bridge/config/extension.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[core]
reloadable = true
order = 0

[package]
version = "0.1.0"
category = "simulation"
title = "Beckhoff Bridge"
description = "A bridge for connecting Omniverse to Beckhoff PLCs over ADS"
authors = ["Loupe"]
repository = "https://github.com/loupeteam/Omniverse_Beckhoff_Bridge_Extension"
keywords = ["Beckhoff", "Digital Twin", "ADS", "PLC"]
changelog = "docs/CHANGELOG.md"
readme = "docs/README.md"
preview_image = "data/preview.png"
icon = "data/icon.png"

[dependencies]
"omni.kit.uiapp" = {}

[python.pipapi]
requirements = ['pyads']
use_online_index = true

[[python.module]]
name = "loupe.simulation.beckhoff_bridge"
public = true

[[test]]
# Extra dependencies only to be used during test run
dependencies = [
"omni.kit.ui_test", # UI testing extension
"omni.usd",
"omni.kit.menu.utils",
"omni.physx"
]
timeout = 60
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions exts/loupe.simulation.beckhoff_bridge/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Changelog

[0.1.0]
- Created with based functionality to setup a connection and send/receive messages with other extensions.
77 changes: 77 additions & 0 deletions exts/loupe.simulation.beckhoff_bridge/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Beckhoff Bridge

The Beckhoff Bridge is an [NVIDIA Omniverse](https://www.nvidia.com/en-us/omniverse/) extension for communicating with [Beckhoff PLCs](https://www.beckhoff.com/en-en/) using the [ADS protocol](https://infosys.beckhoff.com/english.php?content=../content/1033/cx8190_hw/5091854987.html&id=).

# Installation

### Install from registry

This is the preferred method. Open up the extensions manager by navigating to `Window / Extensions`. The extension is available as a "Third Party" extension. Search for `Beckhoff Bridge`, and click the slider to Enable it. Once enabled, the extension will be available as an option in the top menu banner of the Omniverse app.

### Install from source

You can also install from source instead. In order to do so, follow these steps:
- Clone the repo [here](https://github.com/loupeteam/Omniverse_Beckhoff_Bridge_Extension).
- In your Omniverse app, open the extensions manager by navigating to `Window / Extensions`.
- Open the general extension settings, and add a new entry into the `Extension Search Paths` table. This should be the local path to the root of the repo that was just cloned.
- Back in the extensions manager, search for `BECKHOFF BRIDGE`, and enable it.
- Once enabled, the extension will show up as an option in the top menu banner.

# Configuration

You can open the extension by clicking on `Beckhoff Bridge / Open Bridge Settings` from the top menu. The following configuration options are available:

- Enable ADS Client: Enable or disable the ADS client from reading or writing data to the PLC.
- Refresh Rate: The rate at which the ADS client will read data from the PLC in milliseconds.
- PLC AMS Net ID: The AMS Net ID of the PLC to connect to.
- Settings commands: These commands are used to load and save the extension settings as permanent parameters. The Save button backs up the current parameters, and the Load button restores them from the last saved values.

# Usage

Once the extension is enabled, the Beckhoff Bridge will attempt to connect to the PLC.

### Monitoring Extension Status

The status of the extension can be viewed in the `Status` field. Here are the possible messages and their meaning:
- `Disabled`: the enable checkbox is unchecked, and no communication is attempted.
- `Attempting to connect...`: the ADS client is trying to connect to the PLC. Staying in this state for more than a few seconds indicates that there is a problem with the connection.
- `Connected`: the ADS client has successfully established a connection with the PLC.
- `Error writing data to the PLC: [...]`: an error occurred while performing an ADS variable write.
- `Error reading data from the PLC: [...]`: an error occurred while performing an ADS variable read.

### Monitoring Variable Values

Once variable reads are occurring, the `Monitor` pane will show a JSON string with the names and values of the variables being read. This is helpful for troubleshooting.

### Performing read/write operations

The variables on the PLC that should be read or written are specified in a custom user extension or app that uses the API available from the `loupe.simulation.beckhoff_bridge` module.

```python
from loupe.simulation.beckhoff_bridge import BeckhoffBridge

# Instantiate the bridge and register lifecycle subscriptions
beckhoff_bridge = BeckhoffBridge.Manager()
beckhoff_bridge.register_init_callback(on_beckoff_init)
beckhoff_bridge.register_data_callback(on_message)

# This function gets called once on init, and should be used to subscribe to cyclic reads.
def on_beckoff_init( event ):
# Create a list of variable names to be read cyclically, and add to Manager
variables = [ 'MAIN.custom_struct.var1',
'MAIN.custom_struct.var_array[0]',
'MAIN.custom_struct.var_array[1]']

beckhoff_bridge.add_cyclic_read_variables(variables)

# This function is called every time the bridge receives new data
def on_message( event ):
# Read the event data, which includes values for the PLC variables requested
data = event.payload['data']['MAIN']['custom_struct']['var_array']

# In the app's cyclic logic, writes can be performed as follows:
def cyclic():
# Write the value `1` to PLC variable 'MAIN.custom_struct.var1'
beckhoff_bridge.write_variable('MAIN.custom_struct.var1', 1)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
'''
File: **BeckhoffBridge.py**
Copyright (c) 2024 Loupe
https://loupe.team

This file is part of Omniverse_Beckhoff_Bridge_Extension, licensed under the MIT License.

'''

from typing import Callable
import carb.events
import omni.kit.app

EVENT_TYPE_DATA_INIT = carb.events.type_from_string("loupe.simulation.beckhoff_bridge.DATA_INIT")
EVENT_TYPE_DATA_READ = carb.events.type_from_string("loupe.simulation.beckhoff_bridge.DATA_READ")
EVENT_TYPE_DATA_READ_REQ = carb.events.type_from_string("loupe.simulation.beckhoff_bridge.DATA_READ_REQ")
EVENT_TYPE_DATA_WRITE_REQ = carb.events.type_from_string("loupe.simulation.beckhoff_bridge.DATA_WRITE_REQ")

class Manager:
"""
BeckhoffBridge class provides an interface for interacting with the Beckhoff Bridge Extension.
It can be used in Python scripts to read and write variables.

Methods:

register_init_callback( callback : Callable[[carb.events.IEvent], None] ): Registers a callback function for the DATA_INIT event.

register_data_callback( callback : Callable[[carb.events.IEvent], None] ): Registers a callback function for the DATA_READ event.

add_cyclic_read_variables( variable_name_array : list[str]): Adds variables to the cyclic read list.

write_variable( name : str, value : any ): Writes a variable value to the Beckhoff Bridge.
"""

def __init__(self):
"""
Initializes the BeckhoffBridge object.
"""
self._event_stream = omni.kit.app.get_app().get_message_bus_event_stream()
self._callbacks = []

def __del__(self):
"""
Cleans up the event subscriptions.
"""
for callback in self._callbacks:
self._event_stream.remove_subscription(callback)

def register_init_callback( self, callback : Callable[[carb.events.IEvent], None] ):
"""
Registers a callback function for the DATA_INIT event.
The callback is triggered when the Beckhoff Bridge is initialized.
The user should use this event to add cyclic read variables.
This event may get called multiple times in normal operation due to the nature of how extensions are loaded.

Args:
callback (function): The callback function to be registered.

Returns:
None
"""
self._callbacks.append(self._event_stream.create_subscription_to_push_by_type(EVENT_TYPE_DATA_INIT, callback))
callback(None)

def register_data_callback( self, callback : Callable[[carb.events.IEvent], None] ):
"""
Registers a callback function for the DATA_READ event.
The callback is triggered when the Beckhoff Bridge receives new data. The payload contains the updated variables.

Args:
callback (Callable): The callback function to be registered.

example callback:
def on_message( event ):
data = event.payload['data']['MAIN']['custom_struct']['var_array']

Returns:
None
"""
self._callbacks.append(self._event_stream.create_subscription_to_push_by_type(EVENT_TYPE_DATA_READ, callback))

def add_cyclic_read_variables(self, variable_name_array : list[str]):
"""
Adds variables to the cyclic read list.
Variables in the cyclic read list are read from the Beckhoff Bridge at a fixed interval.

Args:
variableList (list): List of variables to be added. ["MAIN.myStruct.myvar1", "MAIN.var2", ...]

Returns:
None
"""
self._event_stream.push(event_type=EVENT_TYPE_DATA_READ_REQ, payload={'variables': variable_name_array})

def write_variable(self, name : str, value : any ):
"""
Writes a variable value to the Beckhoff Bridge.

Args:
name (str): The name of the variable. "MAIN.myStruct.myvar1"
value (basic type): The value to be written. 1, 2.5, "Hello", ...

Returns:
None
"""
payload = {"variables": [{'name': name, 'value': value}]}
self._event_stream.push(event_type=EVENT_TYPE_DATA_WRITE_REQ, payload=payload)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This software contains source code provided by NVIDIA Corporation.
# Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto. Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
#

from .extension import *
Loading