Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
64b4225
changed imports to local version
Jul 20, 2025
137bc68
bugfix in state_change()
Jul 21, 2025
86883df
hack to allow the states to be async function
Jul 21, 2025
784f347
added custom logger
Jul 21, 2025
b075580
Revert "hack to allow the states to be async function"
Jul 23, 2025
51d9cf8
added exception handling
Jul 26, 2025
28ccd21
added/updated function docstrings to new format
Jul 28, 2025
ec65641
added thread stop implementation
Jul 29, 2025
a599378
implemented default state functions
Jul 29, 2025
90f2176
reset command variable to 0 after execution
Jul 31, 2025
2d5e8fa
fix bug where the CommandEn is not updated when exciting offline
Jul 31, 2025
67216c6
ability to stop idle Thread when switching to offline
Jul 31, 2025
286bd34
fix ProcedureReq not being set bei is_default
Aug 1, 2025
048b7be
resetting Command variable after starting
Aug 1, 2025
a2f3de3
disable commands if in idle no procedure is requested
Aug 1, 2025
52889d2
Revert "changed imports to local version"
Aug 1, 2025
65406be
bugfix for disabling commands
Aug 2, 2025
d108e5b
disable restart if no procedure is selected
Aug 2, 2025
f8e3941
add pause and hold enable, set default to False
Aug 9, 2025
e8e05f3
moved from exception event to optional exception callback
Aug 10, 2025
0fd73ad
fixed checking of attributes added .value where it was missing
Aug 11, 2025
6f72e21
added type annotations, added abstract methods to parent classes
Aug 11, 2025
f2c89c9
fix bug where idle thread would not restart after entering offline
Aug 11, 2025
b85b41d
fix: ensure commands are disabled when no procedure is set
Aug 12, 2025
91a626c
add init value
Aug 12, 2025
6ff2957
moved state change call into run_thread function
Aug 13, 2025
9f073b7
added type notation
Aug 14, 2025
6a23727
fix error for start_subscriptions if no subscriptions are added
Aug 15, 2025
09660ca
add ability to add custom data assembly sets to the server
Aug 15, 2025
8e0d8f1
bugfix for custom data assembly
Aug 20, 2025
dc1cba5
added ability to link param op_src_mode to service op_src_mode for ea…
Aug 24, 2025
a0a0d16
BinVlv changed locks to adhere to standard. Added proper handshake fo…
Aug 27, 2025
a898f0b
Fix for StateChannel and SrcChannel not being set. Also insured corre…
Aug 29, 2025
9198b57
don't updated linked op_src_modes for False for Op Commands
Aug 29, 2025
935f231
fix for previous commit
Aug 29, 2025
050e866
Renamed operation_elements to parameter_elements
Aug 29, 2025
3a0eec3
added real operation_elements
Aug 29, 2025
7cf0488
bugfix to OperationSourceModeOperationElements
Sep 1, 2025
83b39d1
added indicator and operation elements, some refactoring
Sep 1, 2025
b38f2c3
stopped non self completing procedures from self completing
Sep 1, 2025
9bf89f1
added operation mode for BinVlv and BinDrv. Refactor OperationSourceM…
Sep 1, 2025
8cb31a2
Moved lock functionality to new class. Some refactoring
Sep 6, 2025
0f06a7e
made var names state_0 and state_1 more consistent. Plus some refacto…
Sep 6, 2025
e1d8b0e
bugfix and changed op mode to start as Aut and src mode to start as Int
Sep 6, 2025
2a52bc8
removed call scheme from service state methods implemented in a599378…
Sep 10, 2025
565278e
Some refactoring, added SUCServiceElement
Sep 19, 2025
3faae68
fixed wrong code
Oct 18, 2025
0ec16e0
better handling command reset
Oct 18, 2025
df5af5e
fix for running state being set to late
Oct 18, 2025
263968c
added new class diagram
Oct 18, 2025
fb46e95
change logger name
Oct 21, 2025
7d6311b
fixes for generation of mtp file
Oct 21, 2025
ae59a18
better handling resetting of Command... commands
Oct 21, 2025
f60cd14
fix for restart bug that didn't stop the old execute thread if the st…
Oct 23, 2025
cea16bc
better implementation for custom data assemblies
Oct 24, 2025
c4fb1b2
fix and refactor for adding node to root
Oct 26, 2025
011c39b
some cleanup
Oct 26, 2025
566a729
added and modified examples to show new and improved functionality
Oct 26, 2025
82afcc5
included simple_pid for active_element pid
Oct 26, 2025
ddb62b1
updated readme to reflect current state
Oct 26, 2025
3f20e9d
example for exception handling
Oct 26, 2025
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
/dist/
__pycache__/
~$*
MTPPy.egg-info/
270 changes: 270 additions & 0 deletions examples/data_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
from opcua import Client

from mtppy.opcua_server_pea import OPCUAServerPEA

from mtppy.indicator_elements import *
from mtppy.operation_elements import *
from mtppy.active_elements import *
from mtppy.suc_data_assembly import SUCDataAssembly
from mtppy.attribute import Attribute

import time

logging.basicConfig(
format='%(asctime)s.%(msecs)03d; [%(levelname)s]; '
'%(module)s.%(funcName)s: %(message)s',
datefmt='%H:%M:%S', level=logging.INFO)
logging.getLogger("opcua").setLevel(logging.ERROR)
logging.getLogger("mtppy.indicator_elements").setLevel(logging.DEBUG)
logging.getLogger("mtppy.operation_elements").setLevel(logging.DEBUG)
logging.getLogger("mtppy.active_elements").setLevel(logging.DEBUG)

_log = logging.getLogger(__name__)


module = OPCUAServerPEA(endpoint="opc.tcp://127.0.0.1:4840/")

### Indicator elements ###
bin_view = BinView('indicator_bin', 'demo object', v_state_0='Off', v_state_1='On')
dint_view = DIntView('indicator_dint', 'demo object', v_scl_min=0, v_scl_max=100, v_unit=1342)
ana_view = AnaView('indicator_ana', 'demo object', v_scl_min=0, v_scl_max=100, v_unit=1342)
str_view = StringView('indicator_str', 'demo object')

# add indicator elements to the module
module.add_indicator_element(bin_view)
module.add_indicator_element(dint_view)
module.add_indicator_element(ana_view)
module.add_indicator_element(str_view)


### Operation elements ###
bin_man = BinMan('op_bin', 'demo object', v_state_0='Off', v_state_1='On', init_value=0)
dint_man = DIntMan('op_dint', 'demo object', v_min=0, v_max=100,
v_scl_min=0, v_scl_max=100, v_unit=1342, init_value=0)
ana_man = AnaMan('op_ana', 'demo object', v_min=0, v_max=100,
v_scl_min=0, v_scl_max=100, v_unit=1342, init_value=0)

# add operation elements to the module
module.add_operation_element(bin_man)
module.add_operation_element(dint_man)
module.add_operation_element(ana_man)


## Internal operation elements ##
bin_man_int = BinManInt('op_bin_int', 'demo object', v_state_0='Off', v_state_1='On', init_value=0)
dint_man_int = DIntManInt('op_dint_int', 'demo object', v_min=0, v_max=100,
v_scl_min=0, v_scl_max=100, v_unit=1342, init_value=0)
ana_man_int = AnaManInt('op_ana_int', 'demo object', v_min=0, v_max=100,
v_scl_min=0, v_scl_max=100, v_unit=1342, init_value=0)

# add internal operation elements to the module
module.add_operation_element(bin_man_int)
module.add_operation_element(dint_man_int)
module.add_operation_element(ana_man_int)


### Active elements ###
bin_vlv = BinVlv('active_bin', 'demo object', safe_pos=0, safe_pos_en=True,
perm_en=True, intl_en=True, prot_en=True)

# add active elements to the module
module.add_active_element(bin_vlv)


### Custom Data Assembly ###
# it is also possible to create custom data assemblies by inheriting from SUCDataAssembly
# Attributes can be using the self._add_attribute() function inside the __init__ function
class CustomDataAssembly(SUCDataAssembly):
def __init__(self, tag_name: str, tag_description: str = '', pol_request: int = 0):
super().__init__(tag_name, tag_description)

self._add_attribute(Attribute('POLRequest', int,
init_value=pol_request, sub_cb=self._set_pol_request))
self._add_attribute(Attribute('PEAResponse', str, init_value='Hello World'))

self.pol_request = pol_request

def set_pea_response(self, value: str):
self.attributes['PEAResponse'].set_value(value)
_log.info(f'PEAResponse set to {value}')

def get_pol_request(self) -> int:
return self.pol_request

def _set_pol_request(self, value: int):
# ignore reset callback
if value == 0:
return
self.pol_request = value
_log.info(f'POLRequest set to {value} ')

if value == 42:
self.set_pea_response('The answer to life, the universe and everything.')
else:
self.set_pea_response(f'POLRequest was {value}.')

self.attributes['POLRequest'].set_value(0) # reset POLRequest after reading


custom_da = CustomDataAssembly('custom_da_1', 'demo object', pol_request=123)
module.add_custom_data_assembly(custom_da, "custom_data_assemblies")

# start the OPC UA server
module.run_opcua_server()

# while True:
# time.sleep(1)

###############################################################################################
# Test
opcua_client = Client(module.endpoint)
opcua_client.connect()
time.sleep(1)


print('--- Set values of indicator elements ---')
bin_view.set_v(1)
dint_view.set_v(55)
ana_view.set_v(33.3)
str_view.set_v('Hello MTPPy!')
time.sleep(1)

print('--- Print values of indicator elements (Simulating HMI side) ---')
print(f"bin_view: {opcua_client.get_node('ns=3;s=indicator_elements.indicator_bin.V').get_value()}")
print(f"dint_view: {opcua_client.get_node('ns=3;s=indicator_elements.indicator_dint.V').get_value()}")
print(f"ana_view: {opcua_client.get_node('ns=3;s=indicator_elements.indicator_ana.V').get_value()}")
print(f"str_view: {opcua_client.get_node('ns=3;s=indicator_elements.indicator_str.V').get_value()}")
time.sleep(2)


print()
print('--- Set values of operation elements (Simulating HMI side) ---')
opcua_client.get_node('ns=3;s=operation_elements.op_bin.VMan').set_value(1)
opcua_client.get_node('ns=3;s=operation_elements.op_dint.VMan').set_value(77)
opcua_client.get_node('ns=3;s=operation_elements.op_ana.VMan').set_value(44.4)
time.sleep(1)

print('--- Print values of operation elements ---')
print(f"bin_man: {bin_man.get_v_out()}")
print(f"dint_man: {dint_man.get_v_out()}")
print(f"ana_man: {ana_man.get_v_out()}")
time.sleep(2)


print()
print('--- Set values of internal operation elements ---')
bin_man_int.set_v_int(1)
dint_man_int.set_v_int(88)
ana_man_int.set_v_int(55.5)
time.sleep(1)

print('--- Print values of internal operation elements ---')
print(
f"bin_man_int: {opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.VOut').get_value()}")
print(
f"dint_man_int: {opcua_client.get_node('ns=3;s=operation_elements.op_dint_int.VOut').get_value()}")
print(
f"ana_man_int: {opcua_client.get_node('ns=3;s=operation_elements.op_ana_int.VOut').get_value()}")
time.sleep(2)

print('--- Set SrcChannel to 0 (operator switches)')
opcua_client.get_node(
'ns=3;s=operation_elements.op_bin_int.op_src_mode.SrcChannel').set_value(False)
print('--- Set to Manual via operator ---')
opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.op_src_mode.SrcManOp').set_value(True)
print('--- Set value via operator ---')
opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.VMan').set_value(0)
time.sleep(1)
print(f"bin_man_int: {bin_man_int.get_v_out()}")
time.sleep(1)

print('--- Set SrcChannel to 1 (internal control) ---')
opcua_client.get_node(
'ns=3;s=operation_elements.op_bin_int.op_src_mode.SrcChannel').set_value(True)
print('--- Set to Automatic via internal control ---')
opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.op_src_mode.SrcIntAut').set_value(True)
print('--- Set value via internal control ---')
bin_man_int.set_v_int(1)
time.sleep(1)
print(
f"bin_man_int: {opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.VOut').get_value()}")
time.sleep(2)


print()
print('--- Set values of active element ---')
bin_vlv.set_open_aut(True)
time.sleep(1)
print(
f"bin_vlv position: {opcua_client.get_node('ns=3;s=active_elements.active_bin.Ctrl').get_value()}")
time.sleep(1)
bin_vlv.set_close_aut(True)
time.sleep(1)
print(
f"bin_vlv position: {opcua_client.get_node('ns=3;s=active_elements.active_bin.Ctrl').get_value()}")
time.sleep(2)

print('--- Demonstrate bin_vlv locks ---')
locks = bin_vlv.locks

print(f"Initial permit status: {locks.permit_status()}")
print(f"Initial interlock status: {locks.interlock_status()}")
print(f"Initial protect status: {locks.protect_status()}")

# Enable and disable permit
locks.set_permit(False)
print(f"Permit status after disabling: {locks.permit_status()}")
bin_vlv.set_open_aut(True)
time.sleep(1)
print(
f"bin_vlv position (should not change): {opcua_client.get_node('ns=3;s=active_elements.active_bin.Ctrl').get_value()}")
time.sleep(1)
locks.set_permit(True)
print(f"Permit status after enabling: {locks.permit_status()}")
time.sleep(1)
bin_vlv.set_open_aut(True)
time.sleep(1)
print(
f"bin_vlv position (should change now): {opcua_client.get_node('ns=3;s=active_elements.active_bin.Ctrl').get_value()}")
time.sleep(2)

# Enable and disable interlock
locks.set_interlock(True)
print(f"Interlock status after enabling: {locks.interlock_status()}")
sleep(1)
print(f"Interlock triggered, valve should close automatically: valve status: {bin_vlv.get_ctrl()}")
locks.set_interlock(False)
print(f"Interlock status after disabling: {locks.interlock_status()}")

# Enable and disable protect
locks.set_protect(True)
print(f"Protect status after enabling: {locks.protect_status()}")
bin_vlv.reset_vlv()
print(f"Protect status after disabling: {locks.protect_status()}")
time.sleep(2)


print()
print('--- Set and read Custom Data Assembly attributes ---')
print("--- Set POLRequest to 42 (Simulating HMI side) ---")
opcua_client.get_node('ns=3;s=custom_data_assemblies.custom_da_1.POLRequest').set_value(42)
time.sleep(1)
print("--- Read POLRequest and PEAResponse attributes ---")
print(f"POLRequest: {custom_da.get_pol_request()}")
print(
f"PEAResponse: {opcua_client.get_node('ns=3;s=custom_data_assemblies.custom_da_1.PEAResponse').get_value()}")
time.sleep(1)
print("--- Try setting the PEAResponse attribute from HMI side (should have no effect) ---")
try:
opcua_client.get_node('ns=3;s=custom_data_assemblies.custom_da_1.PEAResponse').set_value(
'PEAResponse set from from HMI')
except Exception as e:
print(f"Caught exception as expected: {e}")
time.sleep(1)
print(
f"PEAResponse on server (should not be changed): {opcua_client.get_node('ns=3;s=custom_data_assemblies.custom_da_1.PEAResponse').get_value()}")
time.sleep(2)

print('--- Demo complete ---')
opcua_client.disconnect()
module.get_opcua_server().stop()
Loading