├── pyproject.toml ├── examples ├── complex-example │ ├── csvs │ │ ├── ModbusTags_SingleTag.csv │ │ └── ModbusTags.csv │ ├── objs │ │ ├── device.json │ │ ├── tag.json │ │ ├── agent_item.json │ │ ├── channel.json │ │ └── agent.json │ └── setup.json ├── README.md ├── kepware lls config example.py ├── ua server config example.py ├── services and logs example.py ├── user management example.py └── iot gateway example.py ├── docs └── index.html ├── kepconfig ├── connectivity │ ├── udd │ │ ├── __init__.py │ │ └── profile.py │ ├── egd │ │ ├── __init__.py │ │ ├── name.py │ │ └── range.py │ ├── __init__.py │ └── channel.py ├── ua_gateway │ ├── __init__.py │ ├── certificates.py │ └── common.py ├── admin │ ├── __init__.py │ ├── ua_server.py │ ├── users.py │ ├── user_groups.py │ └── lls.py ├── datalogger │ ├── __init__.py │ ├── mapping.py │ ├── log_items.py │ ├── triggers.py │ └── log_group.py ├── iot_gateway │ ├── __init__.py │ └── iot_items.py ├── __init__.py ├── helpers │ └── deprecation_utils.py ├── utils.py ├── adv_tags │ ├── __init__.py │ ├── link_tags.py │ ├── max_tags.py │ ├── min_tags.py │ ├── complex_tags.py │ ├── derived_tags.py │ ├── average_tags.py │ └── cumulative_tags.py ├── error.py └── structures.py ├── tests ├── conftest.py └── udd_test.py ├── LICENSE ├── setup.py ├── .gitignore ├── release.py └── README.md /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /examples/complex-example/csvs/ModbusTags_SingleTag.csv: -------------------------------------------------------------------------------- 1 | Device,Device_IP,TagName,Address,DataType 2 | ModbusDevice,192.168.0.167,Humidity,40003,8 -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/complex-example/objs/device.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "Device1", 3 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Modbus TCP/IP Ethernet", 4 | "servermain.DEVICE_MODEL": 0, 5 | "servermain.DEVICE_ID_STRING": "<132.253.75.52>.0" 6 | } -------------------------------------------------------------------------------- /examples/complex-example/objs/tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "Acceleration_X", 3 | "servermain.TAG_ADDRESS": "40007", 4 | "servermain.TAG_DATA_TYPE": 8, 5 | "servermain.TAG_READ_WRITE_ACCESS": 1, 6 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 7 | "servermain.TAG_SCALING_TYPE": 0 8 | } 9 | -------------------------------------------------------------------------------- /examples/complex-example/setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "Kepware_IP_Array" : ["127.0.0.1"], 3 | "Kepware_Port" : "57412", 4 | "configApiUsername" : "administrator", 5 | "configApiPassword" : "", 6 | "channel_name" : "channelModbus", 7 | "path" : "csvs/ModbusTags.csv", 8 | "Broker_URL" : "tcp://127.0.0.1:1883" 9 | } -------------------------------------------------------------------------------- /examples/complex-example/objs/agent_item.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "Channel1_Device1_Acceleration_X", 3 | "iot_gateway.IOT_ITEM_SERVER_TAG": "Channel1.Device1.Acceleration_X", 4 | "iot_gateway.IOT_ITEM_USE_SCAN_RATE": true, 5 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 10000, 6 | "iot_gateway.IOT_ITEM_SEND_EVERY_SCAN": false, 7 | "iot_gateway.IOT_ITEM_DEADBAND_PERCENT": 0, 8 | "iot_gateway.IOT_ITEM_ENABLED": true, 9 | "iot_gateway.IOT_ITEM_DATA_TYPE": 8 10 | } -------------------------------------------------------------------------------- /kepconfig/connectivity/udd/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`udd` module provides support for Universal Device driver specific objects 8 | within the Kepware Configuration API 9 | """ 10 | 11 | from . import profile -------------------------------------------------------------------------------- /kepconfig/ua_gateway/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) 2023 PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`ua_gateway` module provides support for Kepware's UA Gateway plug-in 8 | specific objects within the Kepware Configuration API 9 | """ 10 | 11 | from . import certificates, client, server -------------------------------------------------------------------------------- /kepconfig/admin/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`admin` module provides functionality to manage Kepware Administration based 8 | properties available through the Kepware Configuration API 9 | """ 10 | 11 | from . import users, user_groups,ua_server, lls -------------------------------------------------------------------------------- /kepconfig/datalogger/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`datalogger` module provides support for Kepware's Datalogger plug-in specific objects 8 | within the Kepware Configuration API 9 | """ 10 | from . import log_group, log_items, triggers, mapping 11 | 12 | -------------------------------------------------------------------------------- /kepconfig/connectivity/egd/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`egd` module provides support for GE EGD driver specific objects 8 | within the Kepware Configuration API 9 | """ 10 | 11 | from . import exchange, range, name 12 | 13 | CONSUMER_EXCHANGE = 'CONSUMER' 14 | PRODUCER_EXCHANGE = 'PRODUCER' 15 | -------------------------------------------------------------------------------- /kepconfig/iot_gateway/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`iot_gateway` module provides support for Kepware's IoT Gateway plug-in 8 | specific objects within the Kepware Configuration API 9 | """ 10 | 11 | from . import agent, iot_items 12 | 13 | #IoT Gateway Agent Types 14 | MQTT_CLIENT_AGENT = 'MQTT Client' 15 | REST_CLIENT_AGENT = 'REST Client' 16 | REST_SERVER_AGENT = 'REST Server' 17 | THINGWORX_AGENT = 'ThingWorx' 18 | -------------------------------------------------------------------------------- /kepconfig/connectivity/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`connectivity` module provides functionality to manage Kepware driver configuration 8 | available through the Kepware Configuration API. This includes channels, devices, 9 | tags, tag groups and driver specific objects. 10 | 11 | Driver specific object support, if available, through their own modules. Currently 12 | the GE Ethernet Global Data and Universal Device Drivers have driver specific API 13 | support in the SDK. 14 | """ 15 | from . import channel, device, tag, egd, udd -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Pytest config file 8 | 9 | import pytest, sys, os 10 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 11 | import kepconfig 12 | 13 | @pytest.fixture(scope="module") 14 | def kepware_server(): 15 | return [kepconfig.connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '', https = False), 'TKS'] 16 | 17 | # server = kepconfig.connection.server(host = '127.0.0.1', port = 57513, user = 'Administrator', pw = '', https = True) 18 | # server.SSL_trust_all_certs = True 19 | # return [server, 'TKE'] 20 | -------------------------------------------------------------------------------- /examples/complex-example/csvs/ModbusTags.csv: -------------------------------------------------------------------------------- 1 | Device,Device_IP,TagName,Address,DataType 2 | ModbusDevice,192.168.0.167,Temperature,40001,8 3 | ModbusDevice,192.168.0.167,Humidity,40003,8 4 | ModbusDevice,192.168.0.167,Pressure,40005,8 5 | ModbusDevice,192.168.0.167,Acceleration_X,40007,8 6 | ModbusDevice,192.168.0.167,Acceleration_Y,40009,8 7 | ModbusDevice,192.168.0.167,Acceleration_Z,40011,8 8 | ModbusDevice,192.168.0.167,Gyroscope_Roll,40013,8 9 | ModbusDevice,192.168.0.167,Gyroscope_Pitch,40015,8 10 | ModbusDevice,192.168.0.167,Gyroscope_Yaw,40017,8 11 | ModbusDevice,192.168.0.167,Orientation_Roll,40019,8 12 | ModbusDevice,192.168.0.167,Orientation_Pitch,40021,8 13 | ModbusDevice,192.168.0.167,Orientation_Yaw,40023,8 14 | ModbusDevice,192.168.0.167,Screen_Control,40025,5 15 | ModbusDevice,192.168.0.167,GoodCount,40026,5 16 | ModbusDevice,192.168.0.167,BadCount,40027,5 17 | ModbusDevice1,192.168.0.168,BadCount,40027,5 -------------------------------------------------------------------------------- /examples/complex-example/objs/channel.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "Channel1", 3 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Modbus TCP/IP Ethernet", 4 | "servermain.CHANNEL_DIAGNOSTICS_CAPTURE": false, 5 | "servermain.CHANNEL_ETHERNET_COMMUNICATIONS_NETWORK_ADAPTER_STRING": "", 6 | "servermain.CHANNEL_WRITE_OPTIMIZATIONS_METHOD": 2, 7 | "servermain.CHANNEL_WRITE_OPTIMIZATIONS_DUTY_CYCLE": 10, 8 | "servermain.CHANNEL_NON_NORMALIZED_FLOATING_POINT_HANDLING": 0, 9 | "servermain.CHANNEL_COMMUNICATIONS_SERIALIZATION_VIRTUAL_NETWORK": 0, 10 | "servermain.CHANNEL_COMMUNICATIONS_SERIALIZATION_TRANSACTIONS_PER_CYCLE": 1, 11 | "servermain.CHANNEL_COMMUNICATIONS_SERIALIZATION_NETWORK_MODE": 0, 12 | "modbus_ethernet.CHANNEL_USE_ONE_OR_MORE_SOCKETS_PER_DEVICE": 1, 13 | "modbus_ethernet.CHANNEL_MAXIMUM_SOCKETS_PER_DEVICE": 1, 14 | "modbus_ethernet.CHANNEL_ETHERNET_PORT_NUMBER": 502, 15 | "modbus_ethernet.CHANNEL_ETHERNET_PROTOCOL": 0 16 | } -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Samples for the Kepware Configuration API SDK 2 | 3 | This directory contains samples showing how to use the various features of the Kepware Config API SDK including configuring and modifying various parameters of Kepware. 4 | 5 | **Note that these samples are tested for Python 3.6+** 6 | 7 | - Simple Connectivity Example 8 | - IoT Gateway Example 9 | - Services and Logs Example 10 | - UA Server Configuration Example 11 | - User Management Configuration Example 12 | - DataLogger Plug-in Example 13 | - GE EGD Driver Exchanges Example 14 | 15 | ## Complex Example 16 | 17 | Further samples with more complex scenarios are contained in the [Complex Example](complex-example) directory, including: 18 | 19 | * Read in setup files and templates to build JSON structures 20 | * Create JSON for channel, devices tags 21 | * Create JSON for iot gateway mqtt agent for each device 22 | * Push all configuration to multiple Kepware instances 23 | -------------------------------------------------------------------------------- /kepconfig/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | # **Kepware Configuration (kepconfig) API Library** 7 | # -- 8 | 9 | # This is a package to help create Python applications to conduct operations with the Kepware Configuration API. 10 | # This package is designed to work with all versions of Kepware that support the Configuration API including 11 | # Thingworx Kepware Server (TKS), Thingworx Kepware Edge (TKE) and KEPServerEX (KEP). 12 | 13 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 14 | # See License.txt in the project root for 15 | # license information. 16 | 17 | r""" 18 | .. include:: ../README.md 19 | 20 | """ 21 | __version__ = "1.4.1" 22 | from . import connection, error 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2024 PTC Inc. and/or all its affiliates. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /kepconfig/helpers/deprecation_utils.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Note: The code within this file was created in total or in part 8 | # with the use of AI tools. 9 | 10 | import warnings 11 | import functools 12 | 13 | def _deprecated(reason): 14 | """ 15 | A decorator to mark functions as deprecated. 16 | 17 | Args: 18 | reason (str): The reason why the function is deprecated. 19 | 20 | Returns: 21 | function: The decorated function that emits a DeprecationWarning when called. 22 | """ 23 | def decorator(func): 24 | @functools.wraps(func) 25 | def wrapper(*args, **kwargs): 26 | warnings.warn( 27 | f"{func.__name__} is deprecated: {reason}", 28 | category=DeprecationWarning, 29 | stacklevel=2 30 | ) 31 | return func(*args, **kwargs) 32 | return wrapper 33 | return decorator 34 | -------------------------------------------------------------------------------- /examples/complex-example/objs/agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "AgentMQTT", 3 | "iot_gateway.AGENTTYPES_TYPE": "MQTT Client", 4 | "iot_gateway.AGENTTYPES_ENABLED": true, 5 | "iot_gateway.MQTT_CLIENT_URL": "tcp://localhost:1883", 6 | "iot_gateway.MQTT_CLIENT_TOPIC": "iotgateway", 7 | "iot_gateway.MQTT_CLIENT_QOS": 1, 8 | "iot_gateway.AGENTTYPES_RATE_MS": 10000, 9 | "iot_gateway.AGENTTYPES_PUBLISH_FORMAT": 0, 10 | "iot_gateway.AGENTTYPES_MAX_EVENTS": 1000, 11 | "iot_gateway.AGENTTYPES_TIMEOUT_S": 5, 12 | "iot_gateway.AGENTTYPES_MESSAGE_FORMAT": 0, 13 | "iot_gateway.AGENTTYPES_STANDARD_TEMPLATE": "timestamp: |SERVERTIMESTAMP|\r\nvalues: |VALUES|\r\n", 14 | "iot_gateway.AGENTTYPES_EXPANSION_OF_VALUES": "id: |TAGNAME|\r\nv: |TAGVALUE|\r\nq: |TAGQUALITY|\r\nt: |TAGTIMESTAMP|\r\n", 15 | "iot_gateway.AGENTTYPES_ADVANCED_TEMPLATE": "{\r\n \"timestamp\": |SERVERTIMESTAMP|,\r\n \"values\": [\r\n |#each VALUES|\r\n {\"id\": \"|TAGNAME|\", \"v\": |VALUE|, \"q\": |QUALITY|, \"t\": |TIMESTAMP| } |#unless @last|,|/unless|\r\n |/each|\r\n ]\r\n}", 16 | "iot_gateway.MQTT_CLIENT_CLIENT_ID": "", 17 | "iot_gateway.MQTT_CLIENT_USERNAME": "", 18 | "iot_gateway.MQTT_CLIENT_PASSWORD": "", 19 | "iot_gateway.MQTT_TLS_VERSION": 0, 20 | "iot_gateway.MQTT_CLIENT_CERTIFICATE": false, 21 | "iot_gateway.MQTT_CLIENT_ENABLE_LAST_WILL": false, 22 | "iot_gateway.MQTT_CLIENT_LAST_WILL_TOPIC": "", 23 | "iot_gateway.MQTT_CLIENT_LAST_WILL_MESSAGE": "", 24 | "iot_gateway.MQTT_CLIENT_ENABLE_WRITE_TOPIC": false, 25 | "iot_gateway.MQTT_CLIENT_WRITE_TOPIC": "iotgateway/write" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /kepconfig/utils.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`utils` provides general utilities to help manage 8 | various objects for Kepware's configuration 9 | """ 10 | 11 | from urllib import parse 12 | 13 | def path_split(path: str): 14 | '''Used to split the standard Kepware address decimal notation into a dict that contains the 15 | *channel*, *device* and *tag_path* keys. 16 | 17 | :param path: standard Kepware address in decimal notation ("ch1.dev1.tg1.tg2.tg3") 18 | :return: dict that contains the "channel", "device" and "tag_path" 19 | :rtype: dict 20 | 21 | Ex: path = "ch1.dev1.tg1.tg2.tg3" 22 | 23 | return = {'channel': 'ch1', 'device': 'dev1', 'tag_path': ['tg1','tg2','tg3']} 24 | 25 | Ex: path = "ch1.dev1" 26 | 27 | return = {'channel': 'ch1', 'device': 'dev1'} 28 | ''' 29 | path_list = path.split('.', 2) 30 | path_obj = {} 31 | for x in range(0, len(path_list)): 32 | if x == 0: 33 | path_obj['channel'] = path_list[0] 34 | elif x == 1: 35 | path_obj['device'] = path_list[1] 36 | else: 37 | path_obj['tag_path'] = path_list[2].split('.') 38 | return path_obj 39 | 40 | def _address_dedecimal(tag_address): 41 | '''Used to handle URL references where decimal notation isn't supported in object names, i.e. IoT Gateway items. 42 | 43 | Replaces `.` with `_` and removes leading `_` for system tag references''' 44 | if tag_address[0] == '_': 45 | tag_address = tag_address[1::] 46 | updated = tag_address.replace('.','_') 47 | return updated 48 | 49 | def _url_parse_object(object): 50 | '''Common url parse to handle reserved characters. Used to convert object 51 | names when building URLs. 52 | 53 | Reserved character list that Kepware allows in object names: :/?#[]@!$&'()*+,;=''' 54 | return parse.quote(object, safe='') 55 | -------------------------------------------------------------------------------- /kepconfig/adv_tags/__init__.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`adv_tags` module provides support for Kepware's Advanced Tags plug-in 8 | specific objects within the Kepware Configuration API 9 | """ 10 | 11 | from ..error import KepError 12 | from . import adv_tag_group, average_tags, derived_tags, complex_tags, cumulative_tags, min_tags, max_tags, link_tags 13 | ADV_TAGS_ROOT = '/project/_advancedtags' 14 | 15 | def _adv_tag_path_split(path: str, *, isItem=False) -> dict: 16 | '''Used to split the standard Kepware address decimal notation into a dict that contains the 17 | advanced tag path components. 18 | 19 | :param path: standard Kepware address in decimal notation ("_advancedtags.tg1.tg2.tg3") 20 | :return: dict that contains the "adv_tag_root" and "tag_path" 21 | :rtype: dict 22 | 23 | Ex: path = "_advancedtags.tg1.tg2.tg3" 24 | 25 | return = {'adv_tag_root': '_advancedtags', 'tag_path': ['tg1','tg2','tg3']} 26 | 27 | Ex: path = "_advancedtags.ch1.dev1" 28 | 29 | return = {'adv_tag_root': '_advancedtags', 'tag_path': ['ch1','dev1']} 30 | ''' 31 | path_list = path.split('.', 2) 32 | if path_list[0] != '_advancedtags': 33 | raise KepError('Error: Invalid advanced tag path - Must start with "_advancedtags"') 34 | 35 | path_obj = {} 36 | for x in range(0, len(path_list)): 37 | if x == 0: 38 | path_obj['adv_tag_root'] = path_list[0] 39 | elif x == 1: 40 | if isItem: 41 | path_obj['tag_path'] = path_list[1:-1] 42 | path_obj['item'] = path_list[-1] 43 | else: 44 | path_obj['tag_path'] = path_list[1:] 45 | return path_obj 46 | 47 | def _create_adv_tags_base_url(base_url, path_obj): 48 | '''Creates url object for the "path_obj" which provides the adv tags tag group structure of Kepware's project tree. Used 49 | to build a part of Kepware Configuration API URL structure 50 | 51 | Returns the advanced tag group specific url when a value is passed as the tag group name. 52 | ''' 53 | url = base_url + ADV_TAGS_ROOT 54 | url += adv_tag_group._create_adv_tags_group_url(path_obj) 55 | 56 | return url -------------------------------------------------------------------------------- /kepconfig/error.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | 8 | r"""`error` Exception classes raised by Kepconfig. 9 | Includes KepError, KepURLError and KepHTTPError 10 | """ 11 | 12 | __all__ = ['KepError', 'KepURLError', 'KepHTTPError'] 13 | 14 | 15 | class KepError(Exception): 16 | '''General Exception class for Kepconfig. 17 | 18 | :param msg: General error message returned in string format. 19 | ''' 20 | def __init__(self, msg): 21 | self.msg = msg 22 | 23 | def __str__(self): 24 | return 'KepError Error: %s' % (self.msg) 25 | 26 | class KepURLError(KepError): 27 | '''Exception class raised by Kepconfig that derives responses from the urllib URLError exceptions. 28 | 29 | :param url: full url path of the request that flagged a URLError exception 30 | :param msg: reason parameter in URLError exception 31 | ''' 32 | def __init__(self, url=None, *args, **kwargs): 33 | super().__init__(*args, **kwargs) 34 | self.url = url 35 | 36 | @property 37 | def reason(self): 38 | return self.msg 39 | 40 | def __str__(self): 41 | return '' % self.reason 42 | 43 | class KepHTTPError(KepError): 44 | '''Exception class raised by Kepconfig that derives responses from the urllib HTTPError 45 | exceptions. This exception class is also a valid HTTP response instance. It behaves 46 | this way because HTTP protocol errors are valid responses, with a status 47 | code, headers, and a body. In some contexts, an application may want to 48 | handle an exception like a regular response. 49 | 50 | :param url: url parameter in HTTPError exception 51 | :param code: HTTP response code parameter in HTTPError exception 52 | :param hdrs: hdrs parameter in HTTPError exception 53 | :param payload: string of HTTP response payload that flagged HTTPError exception 54 | :param msg: msg parameter in HTTPError exception 55 | ''' 56 | def __init__(self, url=None, code=None, hdrs=None, payload=None, *args, **kwargs): 57 | super().__init__(*args, **kwargs) 58 | self.url = url 59 | self.code = code 60 | self.hdrs = hdrs 61 | self.payload = payload 62 | 63 | @property 64 | def reason(self): 65 | return self.msg 66 | 67 | def __str__(self): 68 | return 'HTTP Error %s: %s' % (self.code, self.msg) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Package Setup Script - run this to build the package 8 | # Usage - python setup.py sdist bdist_wheel 9 | # Example: python setup.py 1.0.0 sdist bdist_wheel 10 | 11 | import setuptools 12 | import pathlib, os 13 | 14 | def read(rel_path): 15 | # type: (str) -> str 16 | here = os.path.abspath(os.path.dirname(__file__)) 17 | # intentionally *not* adding an encoding option to open, See: 18 | # https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690 19 | with open(os.path.join(here, rel_path)) as fp: 20 | return fp.read() 21 | 22 | def get_version(rel_path): 23 | # type: (str) -> str 24 | for line in read(rel_path).splitlines(): 25 | if line.startswith('__version__'): 26 | # __version__ = "0.9" 27 | delim = '"' if '"' in line else "'" 28 | return line.split(delim)[1] 29 | raise RuntimeError("Unable to find version string.") 30 | 31 | here = pathlib.Path(__file__).parent.resolve() 32 | 33 | # Get the long description from the README file 34 | long_description = (here / 'README.md').read_text(encoding='utf-8') 35 | 36 | setuptools.setup( 37 | name="kepconfig", 38 | version= get_version("kepconfig/__init__.py"), 39 | author="PTC Inc", 40 | description="SDK package for Kepware Configuration API", 41 | keywords="Kepware, OPC, Configuration, Thingworx", 42 | license="MIT License", 43 | long_description=long_description, 44 | long_description_content_type="text/markdown", 45 | url="https://github.com/PTCInc/Kepware-ConfigAPI-SDK-Python", 46 | project_urls={}, 47 | packages=setuptools.find_packages(), 48 | classifiers=[ 49 | "Development Status :: 5 - Production/Stable", 50 | "License :: OSI Approved :: MIT License", 51 | "Programming Language :: Python", 52 | "Programming Language :: Python :: 3", 53 | "Programming Language :: Python :: 3.9", 54 | "Programming Language :: Python :: 3.10", 55 | "Programming Language :: Python :: 3.11", 56 | "Programming Language :: Python :: 3.12", 57 | "Programming Language :: Python :: 3.13", 58 | "Programming Language :: Python :: 3 :: Only", 59 | "Operating System :: OS Independent", 60 | "Intended Audience :: Manufacturing", 61 | "Intended Audience :: Developers", 62 | ], 63 | python_requires='>=3.9', 64 | ) 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Custom 8 | #*.gitignore 9 | *.pem 10 | _notes 11 | .vscode* 12 | .devcontainer* 13 | 14 | _new* 15 | _test* 16 | _build* 17 | _dev* 18 | # /docs 19 | mkdocs.yml 20 | 21 | # Byte-compiled / optimized / DLL files 22 | __pycache__/ 23 | *.py[cod] 24 | *$py.class 25 | 26 | # C extensions 27 | *.so 28 | 29 | # Distribution / packaging 30 | .Python 31 | build/ 32 | develop-eggs/ 33 | dist/ 34 | downloads/ 35 | eggs/ 36 | .eggs/ 37 | lib/ 38 | lib64/ 39 | parts/ 40 | sdist/ 41 | var/ 42 | wheels/ 43 | pip-wheel-metadata/ 44 | share/python-wheels/ 45 | *.egg-info/ 46 | .installed.cfg 47 | *.egg 48 | MANIFEST 49 | 50 | # PyInstaller 51 | # Usually these files are written by a python script from a template 52 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 53 | *.manifest 54 | *.spec 55 | 56 | # Installer logs 57 | pip-log.txt 58 | pip-delete-this-directory.txt 59 | 60 | # Unit test / coverage reports 61 | htmlcov/ 62 | .tox/ 63 | .nox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *.cover 70 | *.py,cover 71 | .hypothesis/ 72 | .pytest_cache/ 73 | 74 | # Translations 75 | *.mo 76 | *.pot 77 | 78 | # Django stuff: 79 | *.log 80 | local_settings.py 81 | db.sqlite3 82 | db.sqlite3-journal 83 | 84 | # Flask stuff: 85 | instance/ 86 | .webassets-cache 87 | 88 | # Scrapy stuff: 89 | .scrapy 90 | 91 | # Sphinx documentation 92 | docs/_build/ 93 | 94 | # PyBuilder 95 | target/ 96 | 97 | # Jupyter Notebook 98 | .ipynb_checkpoints 99 | 100 | # IPython 101 | profile_default/ 102 | ipython_config.py 103 | 104 | # pyenv 105 | .python-version 106 | 107 | # pipenv 108 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 109 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 110 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 111 | # install all needed dependencies. 112 | #Pipfile.lock 113 | 114 | # celery beat schedule file 115 | celerybeat-schedule 116 | 117 | # SageMath parsed files 118 | *.sage.py 119 | 120 | # Environments 121 | .env 122 | .venv 123 | env/ 124 | venv/ 125 | ENV/ 126 | env.bak/ 127 | venv.bak/ 128 | 129 | # Spyder project settings 130 | .spyderproject 131 | .spyproject 132 | 133 | # Rope project settings 134 | .ropeproject 135 | 136 | # mkdocs documentation 137 | /site 138 | 139 | # mypy 140 | .mypy_cache/ 141 | .dmypy.json 142 | dmypy.json 143 | 144 | # Pyre type checker 145 | .pyre/ 146 | 147 | -------------------------------------------------------------------------------- /kepconfig/ua_gateway/certificates.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) 2023 PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`certificates` exposes an API to allow read access to 8 | UA Gateway plug-in instance certificate objects within the Kepware Configuration API 9 | """ 10 | 11 | from ..connection import server 12 | from ..error import KepHTTPError 13 | from ..ua_gateway.common import _INTER_TYPE, _create_url_cert, INSTANCE_CERTIFICATE 14 | 15 | import warnings 16 | from ..helpers.deprecation_utils import _deprecated 17 | 18 | 19 | # Enable DeprecationWarnings to be visible 20 | warnings.simplefilter('always', DeprecationWarning) 21 | 22 | @_deprecated("This function is deprecated and will be removed in a future release. Use `get_instance_certificate()` in UAG client or server module instead.") 23 | def get_instance_certificate(server: server) -> dict: 24 | ''' 25 | DEPRECATED: This function is deprecated and will be removed in a future release. Use `get_instance_certificate()` 26 | in UAG client or server module instead for Kepware 6.18+. 27 | 28 | Returns the properties of the UAG instance certificate object in the UAG certificate store. 29 | These are UAG instance certificates that are used by UAG for trust purposes in the UA security model. 30 | 31 | :param server: instance of the `server` class 32 | 33 | :return: Dict of properties for the certificate requested 34 | 35 | :raises KepHTTPError: If urllib provides an HTTPError 36 | :raises KepURLError: If urllib provides an URLError 37 | ''' 38 | r = server._config_get(server.url + _create_url_cert(_INTER_TYPE.CERTS, INSTANCE_CERTIFICATE)) 39 | return r.payload 40 | 41 | @_deprecated("This function is deprecated and will be removed in a future release. Use `TBD` instead.") 42 | def reissue_self_signed_instance_certificate(server: server) -> bool: 43 | ''' 44 | DEPRECATED: This function is deprecated and will be removed in a future release. Use `get_instance_certificate()` 45 | in UAG client or server module instead for Kepware 6.18+. 46 | 47 | Deletes and reissues a self-signed UAG instance certificate object in the UAG certificate store. 48 | This is the UAG instance certificate that are used by UAG for trust purposes in the UA security model. 49 | 50 | :param server: instance of the `server` class 51 | 52 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 53 | 54 | :raises KepHTTPError: If urllib provides an HTTPError 55 | :raises KepURLError: If urllib provides an URLError 56 | ''' 57 | r = server._config_del(server.url + _create_url_cert(_INTER_TYPE.CERTS, INSTANCE_CERTIFICATE)) 58 | if r.code == 200: return True 59 | else: 60 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) -------------------------------------------------------------------------------- /release.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | 4 | # Constants to package __init__.py to update version of package 5 | file_location = "./kepconfig/__init__.py" 6 | version_re_string = "([0-9]+)\.([0-9]+)\.([0-9]+(?:[ab][0-9])?)" 7 | version_search = re.compile(r'__version__ = "'+ version_re_string + '"') 8 | version_check = re.compile(version_re_string) 9 | 10 | # Updates the version of package via __init__.py file 11 | def bump_version(): 12 | with open(file_location) as f: 13 | s = f.read() 14 | m = version_search.search(s) 15 | v1, v2, v3 = m.groups() 16 | oldv = "{0}.{1}.{2}".format(v1, v2, v3) 17 | 18 | # Need to provide version explicitly before building and releasing 19 | ans = input("Current version of kepconfig is: {} \nUpdate new version to? (ctrl-c to exit): ".format(oldv)) 20 | if ans: 21 | m = version_check.search(ans) 22 | if (m==None): 23 | print("Version must be in format major.minor.patch (1.0.0 or 1.0.0a1) format. Exiting...") 24 | exit() 25 | newv = ans 26 | else: 27 | print("Please enter updated version number. Exiting...") 28 | exit() 29 | print("\n"+ "Updating " + file_location + " version to {}.".format(newv)) 30 | s = s.replace(oldv, newv) 31 | with open(file_location, "w") as f: 32 | f.write(s) 33 | return newv 34 | 35 | 36 | def release(): 37 | # Update/confirm version of package 38 | v = bump_version() 39 | 40 | # Commit changes to git 41 | ans = input("Version updated, commit changes?(y/n)") 42 | if ans.lower() in ("y", "yes"): 43 | os.system("git add " + file_location) 44 | os.system('git commit -m \"{} Release\"'.format(v)) 45 | os.system("git tag {0}".format(v)) 46 | 47 | # This will push the committed changes and tag to Github for release 48 | ans = input("Change committed, push to Github server? (Y/n)") 49 | if ans.lower() in ("y", "yes"): 50 | os.system("git push --tags") 51 | # os.system("git push --follow-tags") 52 | 53 | # Build Distribution packages for external distribution 54 | ans = input("Build dist packages?(Y/n)") 55 | if ans.lower() in ("y", "yes"): 56 | # os.system("rm -rf dist/*") #Linux 57 | os.system("RMDIR /S /Q dist") #Windows 58 | os.system("python -m build") 59 | 60 | # Upload to pypi. Initially test with pypi test environment at test.pypi.org. Final release to pypi production 61 | # You'll be asked for credentials to authentice updating 62 | ans = input("Upload to pypi?(Y/n)") 63 | if ans.lower() in ("y", "yes"): 64 | ans = input("Push to production?(Y=production pypi/n=test pypi)") 65 | if ans.lower() in ("y", "yes"): 66 | 67 | #Production PyPi Server 68 | os.system("python -m twine upload dist/*") 69 | else: 70 | 71 | # Test PyPi Server 72 | os.system("python -m twine upload --repository testpypi dist/*") 73 | 74 | 75 | if __name__ == "__main__": 76 | release() -------------------------------------------------------------------------------- /examples/kepware lls config example.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Kepware LLS Config Example - Simple example on how to manage properties of a Kepware instance 8 | # to connect to a Local License Server through the Kepware Configuration API 9 | 10 | from kepconfig import connection, error 11 | from kepconfig.admin import lls 12 | 13 | def ErrorHandler(err): 14 | # Generic Handler for exception errors 15 | if isinstance(err, error.KepHTTPError): 16 | print(err.code) 17 | print(err.msg) 18 | print(err.url) 19 | print(err.hdrs) 20 | print(err.payload) 21 | elif isinstance(err, error.KepURLError): 22 | print(err.url) 23 | print(err.reason) 24 | elif isinstance(err, error.KepError): 25 | print(err.msg) 26 | else: 27 | print('Different Exception Received: {}'.format(err)) 28 | 29 | # This creates a server reference that is used to target all modifications of 30 | # the Kepware configuration 31 | server = connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '') 32 | 33 | 34 | 35 | # Retreive the LLS properties from a Kepware instance and return a lls_config class object 36 | try: 37 | LLSconfig = lls.get_lls_config(server) 38 | print("{} - {}".format("Get Local License Server parameters for Kepware instance",LLSconfig)) 39 | except Exception as err: 40 | ErrorHandler(err) 41 | 42 | 43 | # Create a lls_config class object from Dict parameters 44 | JSON_lls_config = { 45 | "libadminsettings.LICENSING_SERVER_PORT": 80, 46 | "libadminsettings.LICENSING_SERVER_NAME": "test_host", 47 | "libadminsettings.LICENSING_CHECK_PERIOD_MINS": 20, 48 | "libadminsettings.LICENSING_SERVER_SSL_PORT": 7777, 49 | "libadminsettings.LICENSING_SERVER_ALLOW_INSECURE_COMMS": True, 50 | "libadminsettings.LICENSING_SERVER_ALLOW_SELF_SIGNED_CERTS": True, 51 | "libadminsettings.LICENSING_CLIENT_ALIAS": "TestAliasName" 52 | } 53 | 54 | try: 55 | print("{} - {}".format("Create Local License Server parameters object from Dict", lls.lls_config(JSON_lls_config))) 56 | except Exception as err: 57 | ErrorHandler(err) 58 | 59 | 60 | # Update lls_config object with new values and update the Kepware instance with new parameters 61 | LLSconfig.server_name = 'HOSTNAME' 62 | try: 63 | print("{} - {}".format("Update Local License Server parameters for Kepware instance",lls.update_lls_config(server, LLSconfig))) 64 | except Exception as err: 65 | ErrorHandler(err) 66 | 67 | # Enable the LLS connection for the Kepware instance 68 | try: 69 | print("{} - {}".format("Enable Local License Server connection for Kepware instance",lls.enable_lls(server))) 70 | except Exception as err: 71 | ErrorHandler(err) 72 | 73 | # Disable the LLS connection for the Kepware instance 74 | try: 75 | print("{} - {}".format("Disable Local License Server connection for Kepware instance",lls.disable_lls(server))) 76 | except Exception as err: 77 | ErrorHandler(err) -------------------------------------------------------------------------------- /kepconfig/structures.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`structures` provides general data structures to help manage 8 | various objects for Kepware's configuration 9 | """ 10 | from enum import Enum 11 | 12 | class KepServiceResponse: 13 | '''A class to represent a return object when calling a "service" API of Kepware. This is 14 | used to return the responses when a "service" is executed appropriately 15 | 16 | :param code: HTTP code returned 17 | 18 | :param message: return from the "service" call 19 | 20 | :param href: URL reference to the JOB that is created by the service API 21 | ''' 22 | 23 | def __init__(self, code: str = '', message: str = '', href: str = ''): 24 | self.code = code 25 | self.message = message 26 | self.href = href 27 | 28 | def __str__(self): 29 | return '{"code": %s, "message": %s, "href": %s}' % (self.code, self.message, self.href) 30 | 31 | class KepServiceStatus: 32 | '''A class to represent a status object when checking on a "service" API job state in Kepware. This is 33 | used to return the status of a "service" job 34 | 35 | :param complete: Boolean of service job completion status 36 | 37 | :param status: Status code of job 38 | 39 | :param message: Error message if service job fails 40 | 41 | ''' 42 | def __init__(self, complete: bool = False, status: str = '', message: str = ''): 43 | self.status = status 44 | self.message = message 45 | self.complete = complete 46 | 47 | def __str__(self): 48 | return '{"complete": %s, "status": %s, "message": %s}' % (self.complete, self.status, self.message) 49 | 50 | class _HttpDataAbstract: 51 | def __init__(self): 52 | self.payload = '' 53 | self.code = '' 54 | self.reason = '' 55 | 56 | class FilterModifierEnum(Enum): 57 | '''Enum class to represent the various filter types that can be used in the Kepware API''' 58 | EQUAL = "eq" 59 | NOTEQUAL = "neq" 60 | GREATERTHAN = "gt" 61 | LESSTTHAN = "lt" 62 | GREATERTHANEQUAL = "gte" 63 | LESSTHANEQUAL = "lte" 64 | CONTAINS = "contains" 65 | NOTCONTAINS = "ncontains" 66 | STARTSWITH = "starts_with" 67 | NOTSTARTSWITH = "nstarts_with" 68 | ENDSWITH = "ends_with" 69 | NOTENDSWITH = "nends_with" 70 | 71 | class FilterFieldEnum(Enum): 72 | ID = "id" 73 | TIMESTAMP = "timestamp" 74 | ACTION = "action" 75 | USER = "user" 76 | INTERFACE = "interface" 77 | DETAILS = "details" 78 | DATA = "data" 79 | 80 | class Filter: 81 | '''A class to represent a filter object when calling the Kepware API. This is used to 82 | filter the results of a GET request for Audit Logs. 83 | 84 | :param name: Name of the object to filter on 85 | 86 | :param type: Type of filter to apply 87 | 88 | :param value: Value to filter on 89 | ''' 90 | def __init__(self, field: FilterFieldEnum = FilterFieldEnum.ID, modifier: FilterModifierEnum = FilterModifierEnum.EQUAL, value: str = ''): 91 | self.field = field 92 | self.modifier = modifier 93 | self.value = value 94 | 95 | def __str__(self): 96 | return '{"field": %s, "modifier": %s, "value": %s}' % (self.field, self.modifier, self.value) 97 | -------------------------------------------------------------------------------- /examples/ua server config example.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # UA Server Configuration Example - Simple example on how to manage a connection and 8 | # execute various calls for configuring the UA Server properties of the Kepware 9 | # Configuration API 10 | 11 | from kepconfig import connection, error 12 | import kepconfig.admin.ua_server as ua_server 13 | 14 | # UA Endpoints to be created with properties that can be configured 15 | uaendpoint1 = { 16 | "common.ALLTYPES_NAME": "DefaultEndpoint3", 17 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_ENABLE": True, 18 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_ADAPTER": "Default", 19 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_PORT": 49331, 20 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_NONE": False, 21 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC128_RSA15": 0, 22 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC256": 0, 23 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC256_SHA256": 2 24 | } 25 | uaendpoint2 = { 26 | "common.ALLTYPES_NAME": "DefaultEndpoint4", 27 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_ENABLE": True, 28 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_ADAPTER": "Default", 29 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_PORT": 49332, 30 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_NONE": False, 31 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC128_RSA15": 0, 32 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC256": 0, 33 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC256_SHA256": 2 34 | } 35 | def ErrorHandler(err): 36 | # Generic Handler for exception errors 37 | if isinstance(err, error.KepHTTPError): 38 | print(err.code) 39 | print(err.msg) 40 | print(err.url) 41 | print(err.hdrs) 42 | print(err.payload) 43 | elif isinstance(err, error.KepURLError): 44 | print(err.url) 45 | print(err.reason) 46 | elif isinstance(err, error.KepError): 47 | print(err.msg) 48 | else: 49 | print('Different Exception Received: {}'.format(err)) 50 | 51 | # This creates a server reference that is used to target all modifications of 52 | # the Kepware configuration 53 | server = connection.server(host = 'localhost', port = 57513, user = 'Administrator', pw = '', https=True) 54 | 55 | # Disabling certificate validation (INSECURE) 56 | server.SSL_ignore_hostname = True 57 | server.SSL_trust_all_certs = True 58 | 59 | # Add the UA Endpoints to Kepware's UA server 60 | try: 61 | print("{} - {}".format("Adding multiple UA Endpoints",ua_server.add_endpoint(server,[uaendpoint1,uaendpoint2]))) 62 | except Exception as err: 63 | ErrorHandler(err) 64 | 65 | # Modify Endpoint to disable all encryption and allow unencrypted connections 66 | 67 | modify_ua = { 68 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_NONE": True, 69 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC128_RSA15": 0, 70 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC256": 0, 71 | "libadminsettings.UACONFIGMANAGER_ENDPOINT_SECURITY_BASIC256_SHA256": 0 72 | } 73 | 74 | try: 75 | print("{} - {}".format("Modify UA Endpoint to remove all encrypted enpoints",ua_server.modify_endpoint(server, modify_ua ,uaendpoint1['common.ALLTYPES_NAME']))) 76 | except Exception as err: 77 | ErrorHandler(err) 78 | 79 | # Delete an Endpoint 80 | try: 81 | print("{} - {}".format("Delete an UA Endpoint",ua_server.del_endpoint(server,uaendpoint2['common.ALLTYPES_NAME']))) 82 | except Exception as err: 83 | ErrorHandler(err) 84 | 85 | # All changes will not update until Kepware is Reinitialized 86 | try: 87 | print("{} - {}".format("Reinitialize Kepware to update UA Endpoint Configuration",server.reinitialize())) 88 | except Exception as err: 89 | ErrorHandler(err) 90 | 91 | # Get all UA Endpoints that are configured 92 | try: 93 | print("{} - {}".format("Get all UA Endpoint Configurations",ua_server.get_all_endpoints(server))) 94 | except Exception as err: 95 | ErrorHandler(err) 96 | 97 | # Delete an Endpoint 98 | try: 99 | print("{} - {}".format("Delete an UA Endpoint",ua_server.del_endpoint(server,uaendpoint1['common.ALLTYPES_NAME']))) 100 | except Exception as err: 101 | ErrorHandler(err) 102 | 103 | 104 | -------------------------------------------------------------------------------- /kepconfig/datalogger/mapping.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`mapping` exposes an API to allow modifications (add, delete, modify) to 8 | column mapping objects in a Datalogger log group within the Kepware Configuration API 9 | """ 10 | 11 | from . import log_group as Log_Group 12 | from ..error import KepError, KepHTTPError 13 | from ..connection import server 14 | from ..utils import _url_parse_object 15 | 16 | MAPPING_ROOT = '/column_mappings' 17 | 18 | def _create_url(mapping = None): 19 | '''Creates url object for the "column_mappings" branch of Kepware's project tree. Used 20 | to build a part of Kepware Configuration API URL structure 21 | 22 | Returns the mapping specific url when a value is passed as the column_mapping name. 23 | ''' 24 | 25 | if mapping == None: 26 | return '{}'.format(MAPPING_ROOT) 27 | else: 28 | return '{}/{}'.format(MAPPING_ROOT, _url_parse_object(mapping)) 29 | 30 | def modify_mapping(server: server, log_group: str, DATA: dict, *, mapping: str = None, force: bool = False) -> bool: 31 | '''Modify a column `"mapping"` object and it's properties in Kepware. If a `"mapping"` is not provided as an input, 32 | you need to identify the column mapping in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 33 | assume that is the column mapping that is to be modified. 34 | 35 | :param server: instance of the `server` class 36 | :param log_group: name of log group for the mapping 37 | :param DATA: Dict of the mapping properties to be modified. 38 | :param mapping: *(optional)* column mapping to modify in the log group. Only needed if not existing in `"DATA"` 39 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 40 | 41 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 42 | 43 | :raises KepHTTPError: If urllib provides an HTTPError 44 | :raises KepURLError: If urllib provides an URLError 45 | ''' 46 | 47 | mapping_data = server._force_update_check(force, DATA) 48 | 49 | if mapping == None: 50 | try: 51 | r = server._config_update(server.url + Log_Group._create_url(log_group) + _create_url(mapping_data['common.ALLTYPES_NAME']), mapping_data) 52 | if r.code == 200: return True 53 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 54 | except KeyError as err: 55 | err_msg = 'Error: No column mapping identified in DATA | Key Error: {}'.format(err) 56 | raise KepError(err_msg) 57 | else: 58 | r = server._config_update(server.url + Log_Group._create_url(log_group) + _create_url(mapping), mapping_data) 59 | if r.code == 200: return True 60 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 61 | 62 | def get_mapping(server: server, log_group: str, mapping: str) -> dict: 63 | '''Returns the properties of the `"mapping"` object. 64 | 65 | :param server: instance of the `server` class 66 | :param log_group: name of log group for the mapping 67 | :param mapping: name of column mapping to retrieve properties 68 | 69 | :return: Dict of properties for the mapping object requested 70 | 71 | :raises KepHTTPError: If urllib provides an HTTPError 72 | :raises KepURLError: If urllib provides an URLError 73 | ''' 74 | r = server._config_get(server.url + Log_Group._create_url(log_group) + _create_url(mapping)) 75 | return r.payload 76 | 77 | def get_all_mappings(server: server, log_group: str, *, options: dict = None) -> list: 78 | '''Returns the properties of all column `"mapping"` objects for a log group. 79 | 80 | :param server: instance of the `server` class 81 | :param log_group: name of log group for the mapping 82 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of mapping items. Options are 'filter', 83 | 'sortOrder', 'sortProperty', 'pageNumber', and 'pageSize'. Only used when exchange_name is not defined. 84 | 85 | :return: list of properties for all mapping items in the log group requested 86 | 87 | :raises KepHTTPError: If urllib provides an HTTPError 88 | :raises KepURLError: If urllib provides an URLError 89 | ''' 90 | r = server._config_get(f'{server.url}{Log_Group._create_url(log_group)}{_create_url()}', params= options) 91 | return r.payload 92 | -------------------------------------------------------------------------------- /tests/udd_test.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # UDD Test - Test to exersice all UDD Profile Library related features 8 | 9 | import os, sys 10 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 11 | import kepconfig 12 | from kepconfig import error, connectivity 13 | import kepconfig.connectivity.udd.profile as uddprofile 14 | import time 15 | import datetime 16 | import pytest 17 | 18 | 19 | # Channel and Device name to be used 20 | profile1 = { 21 | "common.ALLTYPES_NAME": "Profile1", 22 | "common.ALLTYPES_DESCRIPTION": "", 23 | "libudcommon.LIBUDCOMMON_PROFILE_JAVASCRIPT": "function onProfileLoad () {return {version: \"2.0\", mode: \"Client\"};}\nfunction onValidateTag (info) {}\nfunction onTagsRequest (info) {}\nfunction onData (info) {}\n" 24 | } 25 | profile2 = { 26 | "common.ALLTYPES_NAME": "Profile2", 27 | "common.ALLTYPES_DESCRIPTION": "", 28 | "libudcommon.LIBUDCOMMON_PROFILE_JAVASCRIPT": "function onProfileLoad () {return {version: \"2.0\", mode: \"Client\"};}\nfunction onValidateTag (info) {}\nfunction onTagsRequest (info) {}\nfunction onData (info) {}\n" 29 | } 30 | 31 | def ErrorHandler(err): 32 | # Generic Handler for exception errors 33 | if isinstance(err, error.KepHTTPError): 34 | print(err.code) 35 | print(err.msg) 36 | print(err.url) 37 | print(err.hdrs) 38 | print(err.payload) 39 | elif isinstance(err, error.KepURLError): 40 | print(err.url) 41 | print(err.reason) 42 | elif isinstance(err, error.KepError): 43 | print(err.msg) 44 | else: 45 | print('Different Exception Received: {}'.format(err)) 46 | 47 | 48 | def initialize(server: kepconfig.connection.server): 49 | if server_type == 'TKE': pytest.skip("UDD not configurable in {}.".format(server_type), allow_module_level=True) 50 | 51 | try: 52 | server._config_get(server.url +"/doc/drivers/Universal Device/channels") 53 | except Exception as err: 54 | pytest.skip("UDD Driver is not installed", allow_module_level=True) 55 | 56 | def complete(server): 57 | try: 58 | profiles_list = uddprofile.get_all_profiles(server) 59 | [uddprofile.del_profile(server, g['common.ALLTYPES_NAME']) for g in profiles_list] 60 | except Exception as err: 61 | ErrorHandler(err) 62 | 63 | @pytest.fixture(scope="module") 64 | def server(kepware_server): 65 | server = kepware_server[0] 66 | global server_type 67 | server_type = kepware_server[1] 68 | 69 | # Initialize any configuration before testing in module 70 | initialize(server) 71 | 72 | # Everything below yield is run after module tests are completed 73 | yield server 74 | complete(server) 75 | 76 | # 77 | # MAIN TEST SET 78 | # 79 | 80 | 81 | def test_profile_add(server: kepconfig.connection.server): 82 | # Add profile 83 | assert uddprofile.add_profile(server, profile1) 84 | 85 | # Add multi profiles with one failure 86 | assert type(uddprofile.add_profile(server, [profile1, profile2])) == list 87 | 88 | def test_profile_get(server: kepconfig.connection.server): 89 | # Get All Profiles 90 | assert type(uddprofile.get_all_profiles(server)) == list 91 | 92 | # Test Get with Options 93 | # Get All Profiles 94 | ret = uddprofile.get_all_profiles(server, options={'filter': '1'}) 95 | assert type(ret) == list 96 | assert len(ret) == 1 97 | 98 | # Get All Profiles - alternate 99 | assert type(uddprofile.get_profile(server)) == list 100 | 101 | # Test Get with Options 102 | # Get All Profiles - alternate 103 | ret = uddprofile.get_profile(server, options={'filter': '1'}) 104 | assert type(ret) == list 105 | assert len(ret) == 1 106 | 107 | # Get one profile 108 | assert type(uddprofile.get_profile(server, profile1['common.ALLTYPES_NAME'])) == dict 109 | 110 | 111 | def test_profile_modify(server: kepconfig.connection.server): 112 | # Modify Profile 113 | profile1['common.ALLTYPES_DESCRIPTION'] = 'Test Change' 114 | assert uddprofile.modify_profile(server, profile1) 115 | 116 | # Modify Profile with name parameter 117 | change = {'common.ALLTYPES_DESCRIPTION':'Test Change'} 118 | assert uddprofile.modify_profile(server, change, profile1['common.ALLTYPES_NAME'], force=True) 119 | 120 | def test_profile_delete(server: kepconfig.connection.server): 121 | # Modify Profile 122 | assert uddprofile.del_profile(server, profile1['common.ALLTYPES_NAME']) -------------------------------------------------------------------------------- /kepconfig/ua_gateway/common.py: -------------------------------------------------------------------------------- 1 | from ..utils import _url_parse_object 2 | from ..connection import server 3 | from ..error import KepHTTPError 4 | from enum import Enum 5 | 6 | r"""`common` contains common internal functions and constants used by the 7 | `ua_gateway` module. 8 | """ 9 | 10 | CERT_TRUST_KEY = 'ua_gateway.UA_CERTIFICATE_TRUST_STATUS' 11 | 12 | # URL Constants for UA Gateway Module 13 | 14 | UA_GATEWAY_ROOT = '/project/_ua_gateway' 15 | CERT_ROOT = f'{UA_GATEWAY_ROOT}/certificates' 16 | CLIENT_ROOT = f'{UA_GATEWAY_ROOT}/ua_client_interfaces/Client Interface' 17 | CONN_ROOT = f'{CLIENT_ROOT}/ua_client_connections' 18 | CLIENT_CERT_ROOT = f'{CLIENT_ROOT}/certificates' 19 | CLIENT_INST_CERT_ROOT = f'{CLIENT_ROOT}/client_instance_certificates' 20 | SERVER_ROOT = f'{UA_GATEWAY_ROOT}/ua_server_interfaces/Server Interface' 21 | ENDPOINT_ROOT = f'{SERVER_ROOT}/ua_server_endpoints' 22 | SERVER_CERT_ROOT = f'{SERVER_ROOT}/certificates' 23 | SERVER_INST_CERT_ROOT = f'{SERVER_ROOT}/server_instance_certificates' 24 | 25 | 26 | class _INTER_TYPE(Enum): 27 | SERVER = 0 28 | CLIENT = 1 29 | CERTS = 2 30 | 31 | # TODO: DEPRECATED: This constant is deprecated and will be removed in a future release. 32 | INSTANCE_CERTIFICATE = "Instance Certificate" 33 | 34 | def _create_url_cert(interface, certificate = None): 35 | '''Creates url object for the "certificate" branch of Kepware's UA Gateway. Used 36 | to build a part of Kepware Configuration API URL structure 37 | ''' 38 | if interface == _INTER_TYPE.SERVER: 39 | if certificate == None: 40 | return SERVER_CERT_ROOT 41 | else: 42 | return f'{SERVER_CERT_ROOT}/{_url_parse_object(certificate)}' 43 | elif interface == _INTER_TYPE.CLIENT: 44 | if certificate == None: 45 | return CLIENT_CERT_ROOT 46 | else: 47 | return f'{CLIENT_CERT_ROOT}/{_url_parse_object(certificate)}' 48 | # TODO: DEPRECATED: This interface type is deprecated and will be removed in a future release. 49 | else: 50 | if certificate == None: 51 | return CERT_ROOT 52 | else: 53 | return '{}/{}'.format(CERT_ROOT,_url_parse_object(certificate)) 54 | 55 | def _create_url_inst_cert(interface, certificate = None): 56 | '''Creates url object for the "instance certificate" branch of Kepware's UA Gateway interfaces. Used 57 | to build a part of Kepware Configuration API URL structure 58 | ''' 59 | if interface == _INTER_TYPE.SERVER: 60 | if certificate == None: 61 | return SERVER_INST_CERT_ROOT 62 | else: 63 | return f'{SERVER_INST_CERT_ROOT}/{_url_parse_object(certificate)}' 64 | elif interface == _INTER_TYPE.CLIENT: 65 | if certificate == None: 66 | return CLIENT_INST_CERT_ROOT 67 | else: 68 | return f'{CLIENT_INST_CERT_ROOT}/{_url_parse_object(certificate)}' 69 | else: 70 | if certificate == None: 71 | return CERT_ROOT 72 | else: 73 | return '{}/{}'.format(CERT_ROOT,_url_parse_object(certificate)) 74 | 75 | def _change_cert_trust(server: server, inter_type, certificate: str, trust: bool): 76 | DATA = { 77 | CERT_TRUST_KEY: int(trust) 78 | } 79 | 80 | cert_data = server._force_update_check(True, DATA) 81 | r = server._config_update(server.url + _create_url_cert(inter_type, certificate), cert_data) 82 | if r.code == 200: return True 83 | else: 84 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 85 | 86 | def _delete_cert_truststore(server: server, inter_type, certificate: str): 87 | r = server._config_del(server.url + _create_url_cert(inter_type, certificate)) 88 | if r.code == 200: return True 89 | else: 90 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 91 | 92 | def _create_url_server(ua_server_endpoint = None): 93 | '''Creates url object for the "ua_server_endpoints" branch of Kepware's UA Gateway. Used 94 | to build a part of Kepware Configuration API URL structure 95 | 96 | Returns the UA Gateway client connections specific url when a value is passed as the ua client interface name. 97 | ''' 98 | if ua_server_endpoint == None: 99 | return ENDPOINT_ROOT 100 | else: 101 | return f'{ENDPOINT_ROOT}/{_url_parse_object(ua_server_endpoint)}' 102 | 103 | def _create_url_client(ua_client_connection = None): 104 | '''Creates url object for the "ua_client_connections" branch of Kepware's UA Gateway. Used 105 | to build a part of Kepware Configuration API URL structure 106 | 107 | Returns the UA Gateway client connections specific url when a value is passed as the ua client interface name. 108 | ''' 109 | if ua_client_connection == None: 110 | return CONN_ROOT 111 | else: 112 | return f'{CONN_ROOT}/{_url_parse_object(ua_client_connection)}' -------------------------------------------------------------------------------- /examples/services and logs example.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Services and Logs Example - Simple example on how to call services and 8 | # get logs from the Kepware Configuration API 9 | 10 | from kepconfig import connection, error 11 | from kepconfig.connectivity import channel, device 12 | import json 13 | import datetime 14 | 15 | # Channel and Device name to be used 16 | ch_name = 'ControlLogix_Channel' 17 | dev_name = 'Device1' 18 | device_IP = '192.168.1.100' 19 | # Service Response Global 20 | r = {} 21 | 22 | def ErrorHandler(err): 23 | # Generic Handler for exception errors 24 | if isinstance(err, error.KepHTTPError): 25 | print(err.code) 26 | print(err.msg) 27 | print(err.url) 28 | print(err.hdrs) 29 | print(err.payload) 30 | elif isinstance(err, error.KepURLError): 31 | print(err.url) 32 | print(err.reason) 33 | elif isinstance(err, error.KepError): 34 | print(err.msg) 35 | else: 36 | print('Different Exception Received: {}'.format(err)) 37 | 38 | # This creates a server reference that is used to target all modifications of 39 | # the Kepware configuration 40 | server = connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '') 41 | 42 | # This Reinitializes Kepware's Server Runtime process, similar to manually reinitializing 43 | # using the Configuration UI or the Administrator tool. 44 | try: 45 | r = server.reinitialize() 46 | print('{} - {}'.format("Execute Reinitialize Service", r)) 47 | except Exception as err: 48 | ErrorHandler(err) 49 | 50 | # This reads the Reinitialize Service Job Status using the returned KepServiceResponse 51 | 52 | try: 53 | print('{} - {}'.format("Read Reinitialize Service Status", server.service_status(r))) 54 | except Exception as err: 55 | ErrorHandler(err) 56 | 57 | # Add a Channel using the "ControlLogix Driver" with a ControlLogix 5500 family device. 58 | # This will be used to demonstrate the "Auto Tag Generation" service available. 59 | channel_data = { 60 | "common.ALLTYPES_NAME": ch_name, 61 | "common.ALLTYPES_DESCRIPTION": "This is the test channel created", 62 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Allen-Bradley Controllogix Ethernet", 63 | "devices": [ 64 | { 65 | "common.ALLTYPES_NAME": dev_name, 66 | "common.ALLTYPES_DESCRIPTION": "Hello, new description", 67 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Allen-Bradley Controllogix Ethernet", 68 | "servermain.DEVICE_MODEL": 0, 69 | "servermain.DEVICE_ID_STRING": "<{}>,1,0".format(device_IP) 70 | } 71 | ] 72 | } 73 | try: 74 | print("{} - {}".format("Adding Controllogix Channel and Device", channel.add_channel(server,channel_data))) 75 | except Exception as err: 76 | ErrorHandler(err) 77 | 78 | # Execute the "TagGeneration" service available in the Kepware Configuration API 79 | try: 80 | r = device.auto_tag_gen(server, '{}.{}'.format(ch_name, dev_name)) 81 | print("{} - {}".format("Executing ATG for Controllogix Device", r)) 82 | except Exception as err: 83 | ErrorHandler(err) 84 | 85 | # This reads the TagGeneration Service Job Status using the returned KepServiceResponse 86 | try: 87 | print('{} - {}'.format("Read Service Status", server.service_status(r))) 88 | except Exception as err: 89 | ErrorHandler(err) 90 | 91 | # Get Event Log from Kepware instance. 92 | # Time parameters need to be UTC values. 93 | try: 94 | print("{} - {}".format("Here is the last Event Log Entry", json.dumps(server.get_event_log(1), indent=4))) 95 | except Exception as err: 96 | ErrorHandler(err) 97 | try: 98 | print("{} - {}".format("Here are the last 25 entries of the Event Log", json.dumps(server.get_event_log(25, datetime.datetime.fromisoformat('2019-11-03T23:35:23.000'), datetime.datetime.utcnow()), indent=4))) 99 | except Exception as err: 100 | ErrorHandler(err) 101 | 102 | try: 103 | print("{} - {}".format("Here are only the Information entries of the Event Log", json.dumps(server.get_event_log(None, datetime.datetime.fromisoformat('2019-11-03T23:35:23.000'), datetime.datetime.utcnow(), options= {'event': 'Information'}), indent=4))) 104 | except Exception as err: 105 | ErrorHandler(err) 106 | 107 | #Get Configuration API Transaction Log 108 | try: 109 | print("{} - {}".format("Here is the last API Transaction Log Entry", json.dumps(server.get_transaction_log(1), indent=4))) 110 | except Exception as err: 111 | ErrorHandler(err) 112 | 113 | # Export the Project Configuration as JSON from the Kepware Server instance. Provides the same information as saving the 114 | # project file as JSON in the Configuration UI locally. 115 | projectJson = server.export_project_configuration() 116 | try: 117 | print("{} - {}".format("Here is the full Project Configuration as JSON", json.dumps(projectJson, indent=4))) 118 | except Exception as err: 119 | ErrorHandler(err) 120 | 121 | # Import the Project Configuration from JSON. This service acts like a FILE->OPEN action and 122 | # stop communications while the new project replaces the current project in the Kepware runtime. 123 | try: 124 | r = server.import_project_configuration(projectJson) 125 | print("{} - {}".format("Executing Project Import service", r)) 126 | print('{} - {}'.format("Read Service Status", server.service_status(r))) 127 | except Exception as err: 128 | ErrorHandler(err) -------------------------------------------------------------------------------- /kepconfig/admin/ua_server.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c), PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`ua_server` exposes an API to allow modifications (add, delete, modify) to 8 | OPC UA Server endpoints within the Kepware Administration through the Kepware Configuration API 9 | """ 10 | from typing import Union 11 | from ..error import KepHTTPError, KepError 12 | from ..connection import server 13 | from ..utils import _url_parse_object 14 | 15 | 16 | UA_ROOT = '/admin/ua_endpoints' 17 | 18 | def _create_url(endpoint = None): 19 | '''Creates url object for the "server_users" branch of Kepware's project tree. Used 20 | to build a part of Kepware Configuration API URL structure 21 | 22 | Returns the user specific url when a value is passed as the user name. 23 | ''' 24 | 25 | if endpoint == None: 26 | return UA_ROOT 27 | else: 28 | return '{}/{}'.format(UA_ROOT, _url_parse_object(endpoint)) 29 | 30 | def add_endpoint(server: server, DATA: Union[dict, list]) -> Union[bool, list]: 31 | '''Add an `"endpoint"` or multiple `"endpoint"` objects to Kepware UA Server by passing a 32 | list of endpoints to be added all at once. 33 | 34 | :param server: instance of the `server` class 35 | :param DATA: Dict or List of Dicts of the UA Endpoints to add 36 | 37 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 38 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 39 | endpoints added that failed. 40 | 41 | :raises KepHTTPError: If urllib provides an HTTPError 42 | :raises KepURLError: If urllib provides an URLError 43 | ''' 44 | 45 | r = server._config_add(server.url + _create_url(), DATA) 46 | if r.code == 201: return True 47 | elif r.code == 207: 48 | errors = [] 49 | for item in r.payload: 50 | if item['code'] != 201: 51 | errors.append(item) 52 | return errors 53 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 54 | 55 | def del_endpoint(server: server, endpoint: str) -> bool: 56 | '''Delete an `"endpoint"` object in Kepware UA Server 57 | 58 | :param server: instance of the `server` class 59 | :param endpoint: name of endpoint to delete 60 | 61 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 62 | 63 | :raises KepHTTPError: If urllib provides an HTTPError 64 | :raises KepURLError: If urllib provides an URLError 65 | ''' 66 | 67 | r = server._config_del(server.url + _create_url(endpoint)) 68 | if r.code == 200: return True 69 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 70 | 71 | def modify_endpoint(server: server, DATA: dict, endpoint: str = None) -> bool: 72 | '''Modify a `"endpoint"` object and it's properties in Kepware UA Server. If a `"endpoint"` is not provided as an input, 73 | you need to identify the endpoint in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 74 | assume that is the endpoint that is to be modified. 75 | 76 | :param server: instance of the `server` class 77 | :param DATA: Dict of the UA endpoint properties to be modified. 78 | :param endpoint: *(optional)* name of endpoint to modify. Only needed if not existing in `"DATA"` 79 | 80 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 81 | 82 | :raises KepHTTPError: If urllib provides an HTTPError 83 | :raises KepURLError: If urllib provides an URLError 84 | ''' 85 | 86 | if endpoint == None: 87 | try: 88 | r = server._config_update(server.url + _create_url(DATA['common.ALLTYPES_NAME']), DATA) 89 | if r.code == 200: return True 90 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 91 | except KeyError as err: 92 | err_msg = 'Error: No UA Endpoint identified in DATA | Key Error: {}'.format(err) 93 | raise KepError(err_msg) 94 | else: 95 | r = server._config_update(server.url + _create_url(endpoint), DATA) 96 | if r.code == 200: return True 97 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 98 | 99 | def get_endpoint(server: server, endpoint: str) -> dict: 100 | '''Returns the properties of the `"endpoint"` object. 101 | 102 | :param server: instance of the `server` class 103 | :param endpoint: name of endpoint to retrieve 104 | 105 | :return: Dict of properties for the UA endpoint requested 106 | 107 | :raises KepHTTPError: If urllib provides an HTTPError 108 | :raises KepURLError: If urllib provides an URLError 109 | ''' 110 | 111 | r = server._config_get(server.url + _create_url(endpoint)) 112 | return r.payload 113 | 114 | def get_all_endpoints(server: server, *, options: dict = None) -> list: 115 | '''Returns list of all `"endpoint"` objects and their properties. 116 | 117 | :param server: instance of the `server` class 118 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of UA endpoints. Options are 'filter', 119 | 'sortOrder', 'sortProperty', 'pageNumber', and 'pageSize. 120 | 121 | :return: List of properties for all UA endpoints requested 122 | 123 | :raises KepHTTPError: If urllib provides an HTTPError 124 | :raises KepURLError: If urllib provides an URLError 125 | ''' 126 | 127 | r = server._config_get(f'{server.url}{_create_url()}', params= options) 128 | return r.payload -------------------------------------------------------------------------------- /kepconfig/connectivity/udd/profile.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | 8 | r"""`profile` exposes an API to allow modifications (add, delete, modify) to 9 | profile objects for the UDD Profile Library plug-in within the Kepware Configuration API 10 | """ 11 | 12 | from ...connection import server 13 | from ...error import KepHTTPError, KepError 14 | from typing import Union 15 | 16 | PROFILE_ROOT = '/project/_profile_library/profiles' 17 | 18 | def add_profile(server: server, DATA: Union[dict, list]) -> Union[bool, list]: 19 | '''Add a `"profile"` or a list of `"profile"` objects to the UDD Profile Library plug-in for Kepware. 20 | 21 | :param server: instance of the `server` class 22 | :param DATA: Dict or List of Dicts of the profiles to add to the Profile Library 23 | through Kepware Configuration API 24 | 25 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 26 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 27 | profiles added that failed. 28 | 29 | :raises KepHTTPError: If urllib provides an HTTPError 30 | :raises KepURLError: If urllib provides an URLError 31 | ''' 32 | 33 | r = server._config_add(f'{server.url}{PROFILE_ROOT}', DATA) 34 | if r.code == 201: return True 35 | elif r.code == 207: 36 | errors = [] 37 | for item in r.payload: 38 | if item['code'] != 201: 39 | errors.append(item) 40 | return errors 41 | else: 42 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 43 | 44 | def del_profile(server: server, profile_name: str) -> bool: 45 | '''Delete a `"profile"` object in UDD Profile Library plug-in for Kepware. 46 | 47 | :param server: instance of the `server` class 48 | :param profile_name: name of profile 49 | 50 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 51 | 52 | :raises KepHTTPError: If urllib provides an HTTPError 53 | :raises KepURLError: If urllib provides an URLError 54 | ''' 55 | 56 | r = server._config_del(f'{server.url}{PROFILE_ROOT}/{profile_name}') 57 | if r.code == 200: return True 58 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 59 | 60 | def modify_profile(server: server, DATA: dict, profile_name: str = None, force: bool = False) -> bool: 61 | '''Modify a `"profile"` object and it's properties in Kepware. If a `"profile_name"` is not provided as an input, 62 | you need to identify the profile in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 63 | assume that is the profile that is to be modified. 64 | 65 | :param server: instance of the `server` class 66 | :param DATA: Dict or List of Dicts of the profile properties to be modified. 67 | :param profile_name: *(optional)* name of profile to modify. Only needed if not existing in `"DATA"` 68 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 69 | 70 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 71 | 72 | :raises KepHTTPError: If urllib provides an HTTPError 73 | :raises KepURLError: If urllib provides an URLError 74 | ''' 75 | 76 | profile_data = server._force_update_check(force, DATA) 77 | if profile_name == None: 78 | try: 79 | r = server._config_update(f"{server.url}{PROFILE_ROOT}/{profile_data['common.ALLTYPES_NAME']}", profile_data) 80 | if r.code == 200: return True 81 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 82 | except KeyError as err: 83 | err_msg = 'Error: No profile identified in DATA | Key Error: {}'.format(err) 84 | raise KepError(err_msg) 85 | else: 86 | r = server._config_update(f'{server.url}{PROFILE_ROOT}/{profile_name}', profile_data) 87 | if r.code == 200: return True 88 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 89 | 90 | def get_profile(server: server, profile_name: str = None, *, options: dict = None) -> Union[dict, list]: 91 | '''Returns the properties of the profile object or a list of all profiles and their 92 | properties. Will return a list if `"profile_name"` is not provided. 93 | 94 | INPUTS: 95 | 96 | :param server: instance of the `server` class 97 | :param profile_name: *(optional)* name of profile. If not defined, will get all profiles 98 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate when getting a list of profiles. Options are `filter`, 99 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize`. Only used when profile_name is not defined. 100 | 101 | :return: Dict of the profile properties or List of Dicts for all profiles and their properties in the Profile Library 102 | 103 | :raises KepHTTPError: If urllib provides an HTTPError 104 | :raises KepURLError: If urllib provides an URLError 105 | ''' 106 | 107 | if profile_name == None: 108 | r = server._config_get(f'{server.url}{PROFILE_ROOT}', params= options) 109 | else: 110 | r = server._config_get(f'{server.url}{PROFILE_ROOT}/{profile_name}') 111 | return r.payload 112 | 113 | def get_all_profiles(server: server, *, options: dict = None): 114 | '''Returns list of all profile objects and their properties. Returned object is JSON list. 115 | 116 | :param server: instance of the `server` class 117 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate when getting a list of profiles. Options are `filter`, 118 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize`. Only used when profile_name is not defined. 119 | 120 | :return: List of data for all profiles and their properties in the Profile Library 121 | 122 | :raises KepHTTPError: If urllib provides an HTTPError 123 | :raises KepURLError: If urllib provides an URLError 124 | ''' 125 | return get_profile(server, options= options) -------------------------------------------------------------------------------- /kepconfig/admin/users.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c), PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`users` exposes an API to allow modifications (add, delete, modify) to 8 | users within the Kepware Administration User Management through the Kepware Configuration API 9 | """ 10 | from typing import Union 11 | from ..error import KepError, KepHTTPError 12 | from ..connection import server 13 | from ..utils import _url_parse_object 14 | 15 | 16 | USERS_ROOT = '/admin/server_users' 17 | ENABLE_PROPERTY = 'libadminsettings.USERMANAGER_USER_ENABLED' 18 | 19 | def _create_url(user = None): 20 | '''Creates url object for the "server_users" branch of Kepware's project tree. Used 21 | to build a part of Kepware Configuration API URL structure 22 | 23 | Returns the user specific url when a value is passed as the user name. 24 | ''' 25 | 26 | if user == None: 27 | return USERS_ROOT 28 | else: 29 | return '{}/{}'.format(USERS_ROOT, _url_parse_object(user)) 30 | 31 | def add_user(server: server, DATA: Union[dict, list]) -> Union[bool, list]: 32 | '''Add a `"user"` or multiple `"user"` objects to Kepware User Manager by passing a 33 | list of users to be added all at once. 34 | 35 | :param server: instance of the `server` class 36 | :param DATA: Dict or List of Dicts of the users to add 37 | 38 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 39 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 40 | endpoints added that failed. 41 | 42 | :raises KepHTTPError: If urllib provides an HTTPError 43 | :raises KepURLError: If urllib provides an URLError 44 | ''' 45 | 46 | r = server._config_add(server.url + _create_url(), DATA) 47 | if r.code == 201: return True 48 | elif r.code == 207: 49 | errors = [] 50 | for item in r.payload: 51 | if item['code'] != 201: 52 | errors.append(item) 53 | return errors 54 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 55 | 56 | def del_user(server: server, user: str) -> bool: 57 | '''Delete a `"user"` object in Kepware User Manager 58 | 59 | :param server: instance of the `server` class 60 | :param user: name of user to delete 61 | 62 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 63 | 64 | :raises KepHTTPError: If urllib provides an HTTPError 65 | :raises KepURLError: If urllib provides an URLError 66 | ''' 67 | 68 | r = server._config_del(server.url + _create_url(user)) 69 | if r.code == 200: return True 70 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 71 | 72 | def modify_user(server: server , DATA: dict, *, user: str = None) -> bool: 73 | '''Modify a `"user object"` and it's properties in Kepware User Manager. If a `"user"` is not provided as an input, 74 | you need to identify the user in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 75 | assume that is the user that is to be modified. 76 | 77 | :param server: instance of the `server` class 78 | :param DATA: Dict of the user properties to be modified. 79 | :param user: *(optional)* name of user to modify. Only needed if not existing in `"DATA"` 80 | 81 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 82 | 83 | :raises KepHTTPError: If urllib provides an HTTPError 84 | :raises KepURLError: If urllib provides an URLError 85 | ''' 86 | 87 | if user == None: 88 | try: 89 | r = server._config_update(server.url + _create_url(DATA['common.ALLTYPES_NAME']), DATA) 90 | if r.code == 200: return True 91 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 92 | except KeyError as err: 93 | err_msg = 'Error: No User identified in DATA | Key Error: {}'.format(err) 94 | raise KepError(err_msg) 95 | else: 96 | r = server._config_update(server.url + _create_url(user), DATA) 97 | if r.code == 200: return True 98 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 99 | 100 | def get_user(server: server, user: str) -> dict: 101 | '''Returns the properties of the `"user"` object. 102 | 103 | :param server: instance of the `server` class 104 | :param user: name of user to retrieve 105 | 106 | :return: Dict of properties for the user requested 107 | 108 | :raises KepHTTPError: If urllib provides an HTTPError 109 | :raises KepURLError: If urllib provides an URLError 110 | ''' 111 | 112 | r = server._config_get(server.url + _create_url(user)) 113 | return r.payload 114 | 115 | def get_all_users(server: server, *, options: dict = None) -> list: 116 | '''Returns list of all `"user"` objects and their properties. 117 | 118 | :param server: instance of the `server` class 119 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of users. Options are 'filter', 120 | 'sortOrder', 'sortProperty', 'pageNumber', and 'pageSize. 121 | 122 | :return: List of properties for all users 123 | 124 | :raises KepHTTPError: If urllib provides an HTTPError 125 | :raises KepURLError: If urllib provides an URLError 126 | ''' 127 | 128 | r = server._config_get(f'{server.url}{_create_url()}', params= options) 129 | return r.payload 130 | 131 | def enable_user(server: server, user: str) -> bool: 132 | '''Enable the `"user"`. 133 | 134 | :param server: instance of the `server` class 135 | :param user: name of user 136 | 137 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 138 | 139 | :raises KepHTTPError: If urllib provides an HTTPError 140 | :raises KepURLError: If urllib provides an URLError 141 | ''' 142 | DATA = {ENABLE_PROPERTY: True} 143 | return modify_user(server, DATA, user= user) 144 | 145 | def disable_user(server: server, user: str) -> bool: 146 | '''Disable the `"user"`. 147 | 148 | :param server: instance of the `server` class 149 | :param user: name of user 150 | 151 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 152 | 153 | :raises KepHTTPError: If urllib provides an HTTPError 154 | :raises KepURLError: If urllib provides an URLError 155 | ''' 156 | DATA = {ENABLE_PROPERTY: False} 157 | return modify_user(server, DATA, user= user) -------------------------------------------------------------------------------- /kepconfig/datalogger/log_items.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`log_items` exposes an API to allow modifications (add, delete, modify) to 8 | log item (tag) objects in a Datalogger log group within the Kepware Configuration API 9 | """ 10 | from typing import Union 11 | from . import log_group as Log_Group 12 | from ..error import KepError, KepHTTPError 13 | from ..connection import server 14 | from ..utils import _url_parse_object 15 | 16 | LOG_ITEMS_ROOT = '/log_items' 17 | 18 | def _create_url(log_item = None): 19 | '''Creates url object for the "log_item" branch of Kepware's project tree. Used 20 | to build a part of Kepware Configuration API URL structure 21 | 22 | Returns the log_item specific url when a value is passed as the log_item name. 23 | ''' 24 | 25 | if log_item == None: 26 | return '{}'.format(LOG_ITEMS_ROOT) 27 | else: 28 | return '{}/{}'.format(LOG_ITEMS_ROOT, _url_parse_object(log_item)) 29 | 30 | 31 | def add_log_item(server: server, log_group: str, DATA: Union[dict, list]) -> Union[bool, list]: 32 | '''Add a `"log item"` or multiple `"log item"` objects to a log group in Kepware's Datalogger. It can 33 | be used to pass a list of log items to be added all at once. 34 | 35 | :param server: instance of the `server` class 36 | :param log_group: name of log group that the log items will be added 37 | :param DATA: Dict or a list of the log items to add through Kepware Configuration API 38 | 39 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 40 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 41 | log items added that failed. 42 | 43 | :raises KepHTTPError: If urllib provides an HTTPError 44 | :raises KepURLError: If urllib provides an URLError 45 | ''' 46 | 47 | r = server._config_add(server.url + Log_Group._create_url(log_group) + _create_url(), DATA) 48 | if r.code == 201: return True 49 | elif r.code == 207: 50 | errors = [] 51 | for item in r.payload: 52 | if item['code'] != 201: 53 | errors.append(item) 54 | return errors 55 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 56 | 57 | def del_log_item(server: server, log_group: str, log_item: str) -> bool: 58 | '''Delete a `"log item"` object of a log group in Kepware's Datalogger. 59 | 60 | :param server: instance of the `server` class 61 | :param log_group: name of log group that log item exists 62 | :param log_item: name of log item to delete 63 | 64 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 65 | 66 | :raises KepHTTPError: If urllib provides an HTTPError 67 | :raises KepURLError: If urllib provides an URLError 68 | ''' 69 | r = server._config_del(server.url + Log_Group._create_url(log_group) + _create_url(log_item)) 70 | if r.code == 200: return True 71 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 72 | 73 | def modify_log_item(server: server, log_group: str, DATA: dict, *, log_item: str = None, force: bool = False) -> bool: 74 | '''Modify a `"log_item"` object and it's properties in Kepware. If a `"log_item"` is not provided as an input, 75 | you need to identify the log_item in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 76 | assume that is the log_item that is to be modified. 77 | 78 | :param server: instance of the `server` class 79 | :param log_group: name of log group that log item exists 80 | :param DATA: Dict of the log item properties to be modified. 81 | :param log_item: *(optional)* name of log item to modify. Only needed if not existing in `"DATA"` 82 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 83 | 84 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 85 | 86 | :raises KepHTTPError: If urllib provides an HTTPError 87 | :raises KepURLError: If urllib provides an URLError 88 | ''' 89 | 90 | log_item_data = server._force_update_check(force, DATA) 91 | 92 | if log_item == None: 93 | try: 94 | r = server._config_update(server.url + Log_Group._create_url(log_group) + _create_url(log_item_data['common.ALLTYPES_NAME']), log_item_data) 95 | if r.code == 200: return True 96 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 97 | except KeyError as err: 98 | err_msg ='Error: No log item identified in DATA | Key Error: {}'.format(err) 99 | raise KepError(err_msg) 100 | else: 101 | r = server._config_update(server.url + Log_Group._create_url(log_group) + _create_url(log_item), log_item_data) 102 | if r.code == 200: return True 103 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 104 | 105 | def get_log_item(server, log_group, log_item) -> dict: 106 | '''Returns the properties of the `"log item"` object. 107 | 108 | :param server: instance of the `server` class 109 | :param log_group: name of log group that log item exists 110 | :param log_item: name of log item to retrieve 111 | 112 | :return: Dict of properties for the log group requested 113 | 114 | :raises KepHTTPError: If urllib provides an HTTPError 115 | :raises KepURLError: If urllib provides an URLError 116 | ''' 117 | r = server._config_get(server.url + Log_Group._create_url(log_group) + _create_url(log_item)) 118 | return r.payload 119 | 120 | def get_all_log_items(server: server, log_group: str, *, options: dict = None) -> list: 121 | '''Returns the properties of all `"log item"` objects for a log group. 122 | 123 | :param server: instance of the `server` class 124 | :param log_group: name of log group that log item exists 125 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of log groups. Options are 'filter', 126 | 'sortOrder', 'sortProperty', 'pageNumber', and 'pageSize'. Only used when exchange_name is not defined. 127 | 128 | :return: list of properties for all log items in the log group requested 129 | 130 | :raises KepHTTPError: If urllib provides an HTTPError 131 | :raises KepURLError: If urllib provides an URLError 132 | ''' 133 | r = server._config_get(f'{server.url}{Log_Group._create_url(log_group)}{_create_url()}', params= options) 134 | return r.payload 135 | -------------------------------------------------------------------------------- /kepconfig/connectivity/egd/name.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | 8 | r"""`names` exposes an API to allow modifications (add, delete, modify) to 9 | name resolution objects for EGD devices within the Kepware Configuration API 10 | """ 11 | 12 | from ...utils import _url_parse_object, path_split 13 | from ...connection import server 14 | from ...error import KepHTTPError, KepError 15 | from typing import Union 16 | from .. import channel, device 17 | 18 | NAMES_ROOT = '/name_resolution_groups/Name Resolutions/name_resolutions' 19 | 20 | def _create_url(device_path, name = None): 21 | '''Creates url object for the "name resolution" branch of Kepware's project 22 | tree. Used to build a part of Kepware Configuration API URL structure 23 | 24 | Returns the name resolution specific url when a value is passed. 25 | ''' 26 | path_obj = path_split(device_path) 27 | device_root = channel._create_url(path_obj['channel']) + device._create_url(path_obj['device']) 28 | 29 | if name == None: 30 | return '{}/{}'.format(device_root, NAMES_ROOT) 31 | else: 32 | return '{}/{}/{}'.format(device_root, NAMES_ROOT, _url_parse_object(name)) 33 | 34 | def add_name_resolution(server: server, device_path: str, DATA: Union[dict, list]) -> Union[bool, list]: 35 | '''Add a `"name resolution"` or multiple `"name resolution"` objects to Kepware. This allows you to 36 | create a name resolution or multiple name resolutions all in one function, if desired. 37 | 38 | :param server: instance of the `server` class 39 | :param device_path: path to EGD device. Standard Kepware address decimal 40 | notation string such as `"channel1.device1"` 41 | :param DATA: Dict or List of Dicts of name resolutions 42 | expected by Kepware Configuration API 43 | 44 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 45 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 46 | name resolutions added that failed. 47 | 48 | :raises KepHTTPError: If urllib provides an HTTPError 49 | :raises KepURLError: If urllib provides an URLError 50 | ''' 51 | 52 | r = server._config_add(server.url + _create_url(device_path), DATA) 53 | if r.code == 201: return True 54 | elif r.code == 207: 55 | errors = [] 56 | for item in r.payload: 57 | if item['code'] != 201: 58 | errors.append(item) 59 | return errors 60 | else: 61 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 62 | 63 | def del_name_resolution(server: server, device_path: str, name: str) -> bool: 64 | '''Delete a `"name resolution"` object in Kepware. 65 | 66 | :param server: instance of the `server` class 67 | :param device_path: path to EGD device. Standard Kepware address decimal 68 | notation string such as `"channel1.device1"` 69 | :param name: name of name resolution to delete 70 | 71 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 72 | 73 | :raises KepHTTPError: If urllib provides an HTTPError 74 | :raises KepURLError: If urllib provides an URLError 75 | ''' 76 | 77 | r = server._config_del(server.url + _create_url(device_path, name)) 78 | if r.code == 200: return True 79 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 80 | 81 | def modify_name_resolution(server: server, device_path: str, DATA: dict, *, name: str = None, force: bool = False) -> bool: 82 | '''Modify a `"name resolution"` object and it's properties in Kepware. If a `"name"` is not provided as an input, 83 | you need to identify the name resolution in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 84 | assume that is the name resolution that is to be modified. 85 | 86 | :param server: instance of the `server` class 87 | :param device_path: path to EGD device. Standard Kepware address decimal 88 | notation string such as `"channel1.device1"` 89 | :param DATA: Dict of name resolution properties to be modified 90 | :param name: *(optional)* name of name resolution to modify. Only needed if not existing in `"DATA"` 91 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 92 | 93 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 94 | 95 | :raises KepHTTPError: If urllib provides an HTTPError 96 | :raises KepURLError: If urllib provides an URLError 97 | ''' 98 | 99 | name_data = server._force_update_check(force, DATA) 100 | if name == None: 101 | try: 102 | r = server._config_update(server.url + _create_url(device_path, name_data['common.ALLTYPES_NAME']), name_data) 103 | if r.code == 200: return True 104 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 105 | except KeyError as err: 106 | err_msg = f'Error: No name resolution identified in DATA | Key Error: {type(DATA)}' 107 | raise KepError(err_msg) 108 | else: 109 | r = server._config_update(server.url + _create_url(device_path, name), name_data) 110 | if r.code == 200: return True 111 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 112 | 113 | def get_name_resolution(server: server, device_path: str, name: str = None, *, options: dict = None) -> Union[dict, list]: 114 | '''Returns the properties of the `"name resolution"` object or a list of all name resolutions. 115 | 116 | :param server: instance of the `server` class 117 | :param device_path: path to EGD device. Standard Kepware address decimal 118 | notation string such as `"channel1.device1"` 119 | :param DATA: Dict of name resolution properties to be modified 120 | :param name: *(optional)* name of name resolution to retrieve. If not defined, will get all name resolutions 121 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate when getting a list of profiles. Options are `filter`, 122 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize`. Only used when `"name"` is not defined. 123 | 124 | :return: Dict of the name resolution properties or List of Dicts for all name resolutions and their properties 125 | 126 | :raises KepHTTPError: If urllib provides an HTTPError 127 | :raises KepURLError: If urllib provides an URLError 128 | ''' 129 | 130 | if name == None: 131 | r = server._config_get(f'{server.url}{_create_url(device_path)}', params= options) 132 | else: 133 | r = server._config_get(f'{server.url}{_create_url(device_path, name)}') 134 | return r.payload -------------------------------------------------------------------------------- /kepconfig/adv_tags/link_tags.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Note: The code within this file was created in total or in part 8 | # with the use of AI tools. 9 | 10 | r"""`link_tags` exposes an API to allow modifications (add, delete, modify) to 11 | link tag objects within the Kepware Configuration API 12 | """ 13 | 14 | from ..connection import server 15 | from ..error import KepError, KepHTTPError 16 | from ..utils import _url_parse_object 17 | from typing import Union 18 | from .. import adv_tags 19 | 20 | LINK_TAGS_ROOT = '/link_tags' 21 | 22 | def _get_link_tags_url(tag: str = None) -> str: 23 | '''Creates url object for the "link_tags" branch of Kepware's project tree. 24 | 25 | Returns the link tag specific url when a value is passed as the tag name. 26 | ''' 27 | if tag is None: 28 | return LINK_TAGS_ROOT 29 | else: 30 | return f'{LINK_TAGS_ROOT}/{_url_parse_object(tag)}' 31 | 32 | def add_link_tag(server: server, adv_tag_group_path: str, DATA: Union[dict, list]) -> Union[bool, list]: 33 | '''Add `"link_tag"` or multiple `"link_tag"` objects to a specific path in Kepware. 34 | Can be used to pass a list of link tags to be added at one path location. 35 | 36 | :param server: instance of the `server` class 37 | :param adv_tag_group_path: path identifying where to add link tag(s). Standard Kepware address decimal 38 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 39 | :param DATA: Dict or List of Dicts of the link tag(s) to add 40 | 41 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 42 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 43 | link tags added that failed. 44 | 45 | :raises KepHTTPError: If urllib provides an HTTPError 46 | :raises KepURLError: If urllib provides an URLError 47 | ''' 48 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 49 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_link_tags_url() 50 | 51 | r = server._config_add(url, DATA) 52 | if r.code == 201: 53 | return True 54 | elif r.code == 207: 55 | errors = [item for item in r.payload if item['code'] != 201] 56 | return errors 57 | else: 58 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 59 | 60 | def modify_link_tag(server: server, link_tag_path: str, DATA: dict, force: bool = False) -> bool: 61 | '''Modify a `"link_tag"` object and its properties in Kepware. 62 | 63 | :param server: instance of the `server` class 64 | :param link_tag_path: path identifying location and link tag to modify. Standard Kepware address decimal 65 | notation string including the link tag such as "_advancedtags.AdvTagGroup1.LinkTag1" 66 | :param DATA: Dict of the `link_tag` properties to be modified 67 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 68 | 69 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 70 | 71 | :raises KepHTTPError: If urllib provides an HTTPError 72 | :raises KepURLError: If urllib provides an URLError 73 | ''' 74 | link_tag_data = server._force_update_check(force, DATA) 75 | path_obj = adv_tags._adv_tag_path_split(link_tag_path, isItem=True) 76 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_link_tags_url(path_obj['item']) 77 | 78 | r = server._config_update(url, link_tag_data) 79 | if r.code == 200: 80 | return True 81 | else: 82 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 83 | 84 | def del_link_tag(server: server, link_tag_path: str) -> bool: 85 | '''Delete `"link_tag"` object at a specific path in Kepware. 86 | 87 | :param server: instance of the `server` class 88 | :param link_tag_path: path identifying location and link tag to delete. Standard Kepware address decimal 89 | notation string including the link tag such as "_advancedtags.AdvTagGroup1.LinkTag1" 90 | 91 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 92 | 93 | :raises KepHTTPError: If urllib provides an HTTPError 94 | :raises KepURLError: If urllib provides an URLError 95 | ''' 96 | path_obj = adv_tags._adv_tag_path_split(link_tag_path, isItem=True) 97 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_link_tags_url(path_obj['item']) 98 | 99 | r = server._config_del(url) 100 | if r.code == 200: 101 | return True 102 | else: 103 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 104 | 105 | def get_link_tag(server: server, link_tag_path: str) -> dict: 106 | '''Returns the properties of the `"link_tag"` object at a specific path in Kepware. 107 | 108 | :param server: instance of the `server` class 109 | :param link_tag_path: path identifying location and link tag to retrieve. Standard Kepware address decimal 110 | notation string including the link tag such as "_advancedtags.AdvTagGroup1.LinkTag1" 111 | 112 | :return: Dict of data for the link tag requested 113 | 114 | :raises KepHTTPError: If urllib provides an HTTPError 115 | :raises KepURLError: If urllib provides an URLError 116 | ''' 117 | path_obj = adv_tags._adv_tag_path_split(link_tag_path, isItem=True) 118 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_link_tags_url(path_obj['item']) 119 | 120 | r = server._config_get(url) 121 | return r.payload 122 | 123 | def get_all_link_tags(server: server, adv_tag_group_path: str, *, options: dict = None) -> list: 124 | '''Returns the properties of all `"link_tag"` objects at a specific path in Kepware. 125 | 126 | :param server: instance of the `server` class 127 | :param adv_tag_group_path: path identifying location to retrieve link tag list. Standard Kepware address decimal 128 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 129 | :param options: *(optional)* Dict of parameters to filter, sort or paginate the list of link tags. Options are `filter`, 130 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 131 | 132 | :return: List of data for all link tags 133 | 134 | :raises KepHTTPError: If urllib provides an HTTPError 135 | :raises KepURLError: If urllib provides an URLError 136 | ''' 137 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 138 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_link_tags_url() 139 | 140 | r = server._config_get(url, params=options) 141 | return r.payload -------------------------------------------------------------------------------- /examples/user management example.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # User Management Example - Simple example on how to manage a connection and 8 | # execute various calls for configuring the user and user group properties of the Kepware 9 | # Configuration API 10 | 11 | from kepconfig import connection, error 12 | from kepconfig.admin import user_groups, users 13 | 14 | # User Groups 15 | group1 = { 16 | 'common.ALLTYPES_NAME': 'Operators', 17 | "libadminsettings.USERMANAGER_GROUP_ENABLED": True, 18 | "libadminsettings.USERMANAGER_IO_TAG_READ": True, 19 | "libadminsettings.USERMANAGER_IO_TAG_WRITE": True, 20 | "libadminsettings.USERMANAGER_IO_TAG_DYNAMIC_ADDRESSING": True, 21 | "libadminsettings.USERMANAGER_SYSTEM_TAG_READ": True, 22 | "libadminsettings.USERMANAGER_SYSTEM_TAG_WRITE": True, 23 | "libadminsettings.USERMANAGER_INTERNAL_TAG_READ": True, 24 | "libadminsettings.USERMANAGER_INTERNAL_TAG_WRITE": True, 25 | "libadminsettings.USERMANAGER_SERVER_MANAGE_LICENSES": True, 26 | "libadminsettings.USERMANAGER_SERVER_RESET_OPC_DIAGS_LOG": True, 27 | "libadminsettings.USERMANAGER_SERVER_RESET_COMM_DIAGS_LOG": True, 28 | "libadminsettings.USERMANAGER_SERVER_MODIFY_SERVER_SETTINGS": True, 29 | "libadminsettings.USERMANAGER_SERVER_DISCONNECT_CLIENTS": True, 30 | "libadminsettings.USERMANAGER_SERVER_RESET_EVENT_LOG": True, 31 | "libadminsettings.USERMANAGER_SERVER_OPCUA_DOTNET_CONFIGURATION": True, 32 | "libadminsettings.USERMANAGER_SERVER_CONFIG_API_LOG_ACCESS": True, 33 | "libadminsettings.USERMANAGER_SERVER_REPLACE_RUNTIME_PROJECT": True, 34 | "libadminsettings.USERMANAGER_BROWSE_BROWSENAMESPACE": True 35 | } 36 | group2 = { 37 | 'common.ALLTYPES_NAME': 'UA Users', 38 | "libadminsettings.USERMANAGER_GROUP_ENABLED": True, 39 | "libadminsettings.USERMANAGER_IO_TAG_READ": True, 40 | "libadminsettings.USERMANAGER_IO_TAG_WRITE": True, 41 | "libadminsettings.USERMANAGER_IO_TAG_DYNAMIC_ADDRESSING": True, 42 | "libadminsettings.USERMANAGER_SYSTEM_TAG_READ": True, 43 | "libadminsettings.USERMANAGER_SYSTEM_TAG_WRITE": True, 44 | "libadminsettings.USERMANAGER_INTERNAL_TAG_READ": True, 45 | "libadminsettings.USERMANAGER_INTERNAL_TAG_WRITE": True, 46 | "libadminsettings.USERMANAGER_SERVER_MANAGE_LICENSES": False, 47 | "libadminsettings.USERMANAGER_SERVER_RESET_OPC_DIAGS_LOG": False, 48 | "libadminsettings.USERMANAGER_SERVER_RESET_COMM_DIAGS_LOG": False, 49 | "libadminsettings.USERMANAGER_SERVER_MODIFY_SERVER_SETTINGS": True, 50 | "libadminsettings.USERMANAGER_SERVER_DISCONNECT_CLIENTS": False, 51 | "libadminsettings.USERMANAGER_SERVER_RESET_EVENT_LOG": False, 52 | "libadminsettings.USERMANAGER_SERVER_OPCUA_DOTNET_CONFIGURATION": False, 53 | "libadminsettings.USERMANAGER_SERVER_CONFIG_API_LOG_ACCESS": False, 54 | "libadminsettings.USERMANAGER_SERVER_REPLACE_RUNTIME_PROJECT": False, 55 | "libadminsettings.USERMANAGER_BROWSE_BROWSENAMESPACE": True 56 | } 57 | 58 | # Users 59 | user1 = { 60 | "common.ALLTYPES_NAME": "Client1", 61 | "libadminsettings.USERMANAGER_USER_GROUPNAME": "Operators", 62 | "libadminsettings.USERMANAGER_USER_ENABLED": True, 63 | "libadminsettings.USERMANAGER_USER_PASSWORD": "Password123456" 64 | } 65 | user2 = { 66 | "common.ALLTYPES_NAME": "Client2", 67 | "libadminsettings.USERMANAGER_USER_GROUPNAME": "UA Users", 68 | "libadminsettings.USERMANAGER_USER_ENABLED": True, 69 | "libadminsettings.USERMANAGER_USER_PASSWORD": "Password123456" 70 | } 71 | 72 | def ErrorHandler(err): 73 | # Generic Handler for exception errors 74 | if isinstance(err, error.KepHTTPError): 75 | print(err.code) 76 | print(err.msg) 77 | print(err.url) 78 | print(err.hdrs) 79 | print(err.payload) 80 | elif isinstance(err, error.KepURLError): 81 | print(err.url) 82 | print(err.reason) 83 | elif isinstance(err, error.KepError): 84 | print(err.msg) 85 | else: 86 | print('Different Exception Received: {}'.format(err)) 87 | 88 | # This creates a server reference that is used to target all modifications of 89 | # the Kepware configuration 90 | server = connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '') 91 | 92 | # --------------------------------------------- 93 | # User Group Methods 94 | # --------------------------------------------- 95 | 96 | # Add the User Groups with the appropriate parameters 97 | try: 98 | print("{} - {}".format("Add new User Groups", user_groups.add_user_group(server, [group1, group2]))) 99 | except Exception as err: 100 | ErrorHandler(err) 101 | 102 | # Modify permissions on a User Group 103 | # Ex: Prevent Write access for user group 104 | 105 | modify_group = { 106 | "libadminsettings.USERMANAGER_IO_TAG_WRITE": False, 107 | "libadminsettings.USERMANAGER_SYSTEM_TAG_WRITE": False, 108 | "libadminsettings.USERMANAGER_INTERNAL_TAG_WRITE": False 109 | } 110 | 111 | try: 112 | print("{} - {}".format("Modify User Group properties to prevent 'Writes'",user_groups.modify_user_group(server, modify_group, user_group= group1['common.ALLTYPES_NAME']))) 113 | except Exception as err: 114 | ErrorHandler(err) 115 | 116 | # Disable and Enable a user groups 117 | try: 118 | print("{} - {}".format("Disable User Group",user_groups.disable_user_group(server, group1['common.ALLTYPES_NAME']))) 119 | except Exception as err: 120 | ErrorHandler(err) 121 | 122 | try: 123 | print("{} - {}".format("Enable User Group",user_groups.enable_user_group(server, group1['common.ALLTYPES_NAME']))) 124 | except Exception as err: 125 | ErrorHandler(err) 126 | 127 | # --------------------------------------------- 128 | # User Methods 129 | # --------------------------------------------- 130 | 131 | # Add new users with the appropriate parameters 132 | try: 133 | print("{} - {}".format("Add new Users", users.add_user(server, [user1, user2]))) 134 | except Exception as err: 135 | ErrorHandler(err) 136 | 137 | # Modify new user parameters - New Password 138 | modify_pass = { 139 | "libadminsettings.USERMANAGER_USER_PASSWORD": "NewPassword123" 140 | } 141 | 142 | try: 143 | print("{} - {}".format("Updated a user password", users.modify_user(server,modify_pass, user= user1['common.ALLTYPES_NAME']))) 144 | except Exception as err: 145 | ErrorHandler(err) 146 | 147 | # Disable and Enable a user 148 | try: 149 | print("{} - {}".format("Disable a user", users.disable_user(server, user1['common.ALLTYPES_NAME']))) 150 | except Exception as err: 151 | ErrorHandler(err) 152 | try: 153 | print("{} - {}".format("Enable a user", users.enable_user(server, user1['common.ALLTYPES_NAME']))) 154 | except Exception as err: 155 | ErrorHandler(err) -------------------------------------------------------------------------------- /kepconfig/datalogger/triggers.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`triggers` exposes an API to allow modifications (add, delete, modify) to 8 | trigger objects in a Datalogger log group within the Kepware Configuration API 9 | """ 10 | 11 | from typing import Union 12 | from . import log_group as Log_Group 13 | from ..error import KepError, KepHTTPError 14 | from ..connection import server 15 | from ..utils import _url_parse_object 16 | 17 | TRIGGERS_ROOT = '/triggers' 18 | 19 | def _create_url(trigger = None): 20 | '''Creates url object for the "trigger" branch of Kepware's project tree. Used 21 | to build a part of Kepware Configuration API URL structure 22 | 23 | Returns the trigger specific url when a value is passed as the trigger name. 24 | ''' 25 | 26 | if trigger == None: 27 | return '{}'.format(TRIGGERS_ROOT) 28 | else: 29 | return '{}/{}'.format(TRIGGERS_ROOT, _url_parse_object(trigger)) 30 | 31 | 32 | def add_trigger(server: server, log_group: str, DATA: Union[dict, list]) -> Union[bool, list]: 33 | '''Add a `"trigger"` or multiple `"trigger"` objects to a log group in Kepware's Datalogger. It can 34 | be used to pass a list of triggers to be added all at once. 35 | 36 | :param server: instance of the `server` class 37 | :param log_group: name of log group for the trigger items 38 | :param DATA: Dict or a list of the trigger items to add through Kepware Configuration API 39 | 40 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 41 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 42 | triggers added that failed. 43 | 44 | :raises KepHTTPError: If urllib provides an HTTPError 45 | :raises KepURLError: If urllib provides an URLError 46 | ''' 47 | 48 | r = server._config_add(server.url + Log_Group._create_url(log_group) + _create_url(), DATA) 49 | if r.code == 201: return True 50 | elif r.code == 207: 51 | errors = [] 52 | for item in r.payload: 53 | if item['code'] != 201: 54 | errors.append(item) 55 | return errors 56 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 57 | 58 | def del_trigger(server: server, log_group: str, trigger: str) -> bool: 59 | '''Delete a `"trigger"` object of a log group in Kepware's Datalogger. 60 | 61 | :param server: instance of the `server` class 62 | :param log_group: name of log group for the trigger items 63 | :param trigger: name of trigger to delete 64 | 65 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 66 | 67 | :raises KepHTTPError: If urllib provides an HTTPError 68 | :raises KepURLError: If urllib provides an URLError 69 | ''' 70 | r = server._config_del(server.url + Log_Group._create_url(log_group) + _create_url(trigger)) 71 | if r.code == 200: return True 72 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 73 | 74 | def modify_trigger(server: server, log_group: str, DATA: dict, *, trigger: str = None, force: bool = False) -> bool: 75 | '''Modify a `"trigger"` object and it's properties in Kepware. If a `"trigger"` is not provided as an input, 76 | you need to identify the trigger in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 77 | assume that is the trigger that is to be modified. 78 | 79 | :param server: instance of the `server` class 80 | :param log_group: name of log group for the trigger items 81 | :param DATA: Dict of the trigger properties to be modified. 82 | :param trigger: *(optional)* name of trigger to modify in the log group. Only needed if not existing in `"DATA"` 83 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 84 | 85 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 86 | 87 | :raises KepHTTPError: If urllib provides an HTTPError 88 | :raises KepURLError: If urllib provides an URLError 89 | ''' 90 | 91 | trigger_data = server._force_update_check(force, DATA) 92 | 93 | if trigger == None: 94 | try: 95 | r = server._config_update(server.url + Log_Group._create_url(log_group) + _create_url(trigger_data['common.ALLTYPES_NAME']), trigger_data) 96 | if r.code == 200: return True 97 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 98 | except KeyError as err: 99 | err_msg = 'Error: No trigger identified in DATA | Key Error: {}'.format(err) 100 | raise KepError(err_msg) 101 | # except: 102 | # return 'Error: Error with {}'.format(inspect.currentframe().f_code.co_name) 103 | else: 104 | r = server._config_update(server.url + Log_Group._create_url(log_group) + _create_url(trigger), trigger_data) 105 | if r.code == 200: return True 106 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 107 | 108 | def get_trigger(server, log_group, trigger) -> dict: 109 | '''Returns the properties of the `"trigger"` object. 110 | 111 | :param server: instance of the `server` class 112 | :param log_group: name of log group for the trigger items 113 | :param trigger: name of trigger to retrieve 114 | 115 | :return: Dict of properties for the trigger requested 116 | 117 | :raises KepHTTPError: If urllib provides an HTTPError 118 | :raises KepURLError: If urllib provides an URLError 119 | ''' 120 | r = server._config_get(server.url + Log_Group._create_url(log_group) + _create_url(trigger)) 121 | return r.payload 122 | 123 | def get_all_triggers(server: server, log_group: str, *, options: dict = None) -> list: 124 | '''Returns the properties of all `"trigger"` objects for a log group. 125 | 126 | :param server: instance of the `server` class 127 | :param log_group: name of log group for the trigger items 128 | 129 | :return: Dict of properties for the trigger requested 130 | 131 | :raises KepHTTPError: If urllib provides an HTTPError 132 | :raises KepURLError: If urllib provides an URLError 133 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of triggers. Options are 'filter', 134 | 'sortOrder', 'sortProperty', 'pageNumber', and 'pageSize'. Only used when exchange_name is not defined. 135 | 136 | :return: list of properties for all triggers in the log group requested 137 | 138 | :raises KepHTTPError: If urllib provides an HTTPError 139 | :raises KepURLError: If urllib provides an URLError 140 | ''' 141 | r = server._config_get(f'{server.url}{Log_Group._create_url(log_group)}{_create_url()}', params= options) 142 | return r.payload 143 | -------------------------------------------------------------------------------- /kepconfig/admin/user_groups.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c), PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`user_groups` exposes an API to allow modifications (add, delete, modify) to 8 | user groups within the Kepware Administration User Manager through the Kepware Configuration API 9 | """ 10 | from typing import Union 11 | from ..error import KepHTTPError, KepError 12 | from ..connection import server 13 | from ..utils import _url_parse_object 14 | 15 | 16 | USERGROUPS_ROOT = '/admin/server_usergroups' 17 | ENABLE_PROPERTY = 'libadminsettings.USERMANAGER_GROUP_ENABLED' 18 | 19 | def _create_url(user_group = None): 20 | '''Creates url object for the "server_usergroups" branch of Kepware's project tree. Used 21 | to build a part of Kepware Configuration API URL structure 22 | 23 | Returns the user group specific url when a value is passed as the user_group name. 24 | ''' 25 | 26 | if user_group == None: 27 | return USERGROUPS_ROOT 28 | else: 29 | return '{}/{}'.format(USERGROUPS_ROOT, _url_parse_object(user_group)) 30 | 31 | def add_user_group(server: server, DATA: Union[dict, list]) -> Union[bool, list]: 32 | '''Add a `"user group"` or multiple `"user group"` objects to Kepware User Manager by passing a 33 | list of user groups to be added all at once. 34 | 35 | :param server: instance of the `server` class 36 | :param DATA: Dict or List of Dicts of the user groups to add 37 | 38 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 39 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 40 | endpoints added that failed. 41 | 42 | :raises KepHTTPError: If urllib provides an HTTPError 43 | :raises KepURLError: If urllib provides an URLError 44 | ''' 45 | 46 | r = server._config_add(server.url + _create_url(), DATA) 47 | if r.code == 201: return True 48 | elif r.code == 207: 49 | errors = [] 50 | for item in r.payload: 51 | if item['code'] != 201: 52 | errors.append(item) 53 | return errors 54 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 55 | 56 | def del_user_group(server: server, user_group: str) -> bool: 57 | '''Delete a `"user group"` object in Kepware User Manager 58 | 59 | :param server: instance of the `server` class 60 | :param user_group: name of user group to delete 61 | 62 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 63 | 64 | :raises KepHTTPError: If urllib provides an HTTPError 65 | :raises KepURLError: If urllib provides an URLError 66 | ''' 67 | 68 | r = server._config_del(server.url + _create_url(user_group)) 69 | if r.code == 200: return True 70 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 71 | 72 | def modify_user_group(server: server, DATA: dict, *, user_group: str = None) -> bool: 73 | '''Modify a `"user group"` object and it's properties in Kepware User Manager. If a `"user group"` is not provided as an input, 74 | you need to identify the user group in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 75 | assume that is the user group that is to be modified. 76 | 77 | :param server: instance of the `server` class 78 | :param DATA: Dict of the user group properties to be modified. 79 | :param user_group: *(optional)* name of user group to modify. Only needed if not existing in `"DATA"` 80 | 81 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 82 | 83 | :raises KepHTTPError: If urllib provides an HTTPError 84 | :raises KepURLError: If urllib provides an URLError 85 | ''' 86 | 87 | if user_group == None: 88 | try: 89 | r = server._config_update(server.url + _create_url(DATA['common.ALLTYPES_NAME']), DATA) 90 | if r.code == 200: return True 91 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 92 | except KeyError as err: 93 | err_msg = 'Error: No User Group identified in DATA | Key Error: {}'.format(err) 94 | raise KepError(err_msg) 95 | else: 96 | r = server._config_update(server.url + _create_url(user_group), DATA) 97 | if r.code == 200: return True 98 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 99 | 100 | def get_user_group(server: server, user_group: str) -> dict: 101 | '''Returns the properties of the `"user group"` object. 102 | 103 | :param server: instance of the `server` class 104 | :param user_group: name of user group to retrieve 105 | 106 | :return: Dict of properties for the user group requested 107 | 108 | :raises KepHTTPError: If urllib provides an HTTPError 109 | :raises KepURLError: If urllib provides an URLError 110 | ''' 111 | 112 | r = server._config_get(server.url + _create_url(user_group)) 113 | return r.payload 114 | 115 | def get_all_user_groups(server: server, *, options: dict = None) -> list: 116 | '''Returns list of all `"user group"` objects and their properties. 117 | 118 | :param server: instance of the `server` class 119 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of user groups. Options are 'filter', 120 | 'sortOrder', 'sortProperty', 'pageNumber', and 'pageSize. 121 | 122 | :return: List of properties for all user groups 123 | 124 | :raises KepHTTPError: If urllib provides an HTTPError 125 | :raises KepURLError: If urllib provides an URLError 126 | ''' 127 | 128 | r = server._config_get(f'{server.url}{_create_url()}', params= options) 129 | return r.payload 130 | 131 | def enable_user_group(server: server, user_group: str) -> bool: 132 | '''Enable the `"user group"`. 133 | 134 | :param server: instance of the `server` class 135 | :param user_group: name of user group 136 | 137 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 138 | 139 | :raises KepHTTPError: If urllib provides an HTTPError 140 | :raises KepURLError: If urllib provides an URLError 141 | ''' 142 | DATA = {ENABLE_PROPERTY: True} 143 | return modify_user_group(server, DATA, user_group= user_group) 144 | 145 | def disable_user_group(server: server, user_group: str) -> bool: 146 | '''Disable the `"user group"`. 147 | 148 | :param server: instance of the `server` class 149 | :param user_group: name of user group 150 | 151 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 152 | 153 | :raises KepHTTPError: If urllib provides an HTTPError 154 | :raises KepURLError: If urllib provides an URLError 155 | ''' 156 | DATA = {ENABLE_PROPERTY: False} 157 | return modify_user_group(server, DATA, user_group= user_group) -------------------------------------------------------------------------------- /kepconfig/adv_tags/max_tags.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Note: The code within this file was created in total or in part 8 | # with the use of AI tools. 9 | 10 | r"""`maximum_tags` exposes an API to allow modifications (add, delete, modify) to 11 | maximum tag objects within the Kepware Configuration API 12 | """ 13 | 14 | from ..connection import server 15 | from ..error import KepError, KepHTTPError 16 | from ..utils import _url_parse_object 17 | from typing import Union 18 | from .. import adv_tags 19 | 20 | MAXIMUM_TAGS_ROOT = '/maximum_tags' 21 | 22 | def _get_maximum_tags_url(tag: str = None) -> str: 23 | '''Creates url object for the "maximum_tags" branch of Kepware's project tree. 24 | 25 | Returns the maximum tag specific url when a value is passed as the tag name. 26 | ''' 27 | if tag is None: 28 | return MAXIMUM_TAGS_ROOT 29 | else: 30 | return f'{MAXIMUM_TAGS_ROOT}/{_url_parse_object(tag)}' 31 | 32 | def add_maximum_tag(server: server, adv_tag_group_path: str, DATA: Union[dict, list]) -> Union[bool, list]: 33 | '''Add `"maximum_tag"` or multiple `"maximum_tag"` objects to a specific path in Kepware. 34 | Can be used to pass a list of maximum tags to be added at one path location. 35 | 36 | :param server: instance of the `server` class 37 | :param adv_tag_group_path: path identifying where to add maximum tag(s). Standard Kepware address decimal 38 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 39 | :param DATA: Dict or List of Dicts of the maximum tag(s) to add 40 | 41 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 42 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 43 | maximum tags added that failed. 44 | 45 | :raises KepHTTPError: If urllib provides an HTTPError 46 | :raises KepURLError: If urllib provides an URLError 47 | ''' 48 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 49 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_maximum_tags_url() 50 | 51 | r = server._config_add(url, DATA) 52 | if r.code == 201: 53 | return True 54 | elif r.code == 207: 55 | errors = [item for item in r.payload if item['code'] != 201] 56 | return errors 57 | else: 58 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 59 | 60 | def modify_maximum_tag(server: server, max_tag_path: str, DATA: dict, force: bool = False) -> bool: 61 | '''Modify a `"maximum_tag"` object and its properties in Kepware. 62 | 63 | :param server: instance of the `server` class 64 | :param max_tag_path: path identifying location and maximum tag to modify. Standard Kepware address decimal 65 | notation string including the maximum tag such as "_advancedtags.AdvTagGroup1.MaxTag1" 66 | :param DATA: Dict of the `maximum_tag` properties to be modified 67 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 68 | 69 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 70 | 71 | :raises KepHTTPError: If urllib provides an HTTPError 72 | :raises KepURLError: If urllib provides an URLError 73 | ''' 74 | max_tag_data = server._force_update_check(force, DATA) 75 | path_obj = adv_tags._adv_tag_path_split(max_tag_path, isItem=True) 76 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_maximum_tags_url(path_obj['item']) 77 | 78 | r = server._config_update(url, max_tag_data) 79 | if r.code == 200: 80 | return True 81 | else: 82 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 83 | 84 | def del_maximum_tag(server: server, max_tag_path: str) -> bool: 85 | '''Delete `"maximum_tag"` object at a specific path in Kepware. 86 | 87 | :param server: instance of the `server` class 88 | :param max_tag_path: path identifying location and maximum tag to delete. Standard Kepware address decimal 89 | notation string including the maximum tag such as "_advancedtags.AdvTagGroup1.MaxTag1" 90 | 91 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 92 | 93 | :raises KepHTTPError: If urllib provides an HTTPError 94 | :raises KepURLError: If urllib provides an URLError 95 | ''' 96 | path_obj = adv_tags._adv_tag_path_split(max_tag_path, isItem=True) 97 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_maximum_tags_url(path_obj['item']) 98 | 99 | r = server._config_del(url) 100 | if r.code == 200: 101 | return True 102 | else: 103 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 104 | 105 | def get_maximum_tag(server: server, max_tag_path: str) -> dict: 106 | '''Returns the properties of the `"maximum_tag"` object at a specific path in Kepware. 107 | 108 | :param server: instance of the `server` class 109 | :param max_tag_path: path identifying location and maximum tag to retrieve. Standard Kepware address decimal 110 | notation string including the maximum tag such as "_advancedtags.AdvTagGroup1.MaxTag1" 111 | 112 | :return: Dict of data for the maximum tag requested 113 | 114 | :raises KepHTTPError: If urllib provides an HTTPError 115 | :raises KepURLError: If urllib provides an URLError 116 | ''' 117 | path_obj = adv_tags._adv_tag_path_split(max_tag_path, isItem=True) 118 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_maximum_tags_url(path_obj['item']) 119 | 120 | r = server._config_get(url) 121 | return r.payload 122 | 123 | def get_all_maximum_tags(server: server, adv_tag_group_path: str, *, options: dict = None) -> list: 124 | '''Returns the properties of all `"maximum_tag"` objects at a specific path in Kepware. 125 | 126 | :param server: instance of the `server` class 127 | :param adv_tag_group_path: path identifying location to retrieve maximum tag list. Standard Kepware address decimal 128 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 129 | :param options: *(optional)* Dict of parameters to filter, sort or paginate the list of maximum tags. Options are `filter`, 130 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 131 | 132 | :return: List of data for all maximum tags 133 | 134 | :raises KepHTTPError: If urllib provides an HTTPError 135 | :raises KepURLError: If urllib provides an URLError 136 | ''' 137 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 138 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_maximum_tags_url() 139 | 140 | r = server._config_get(url, params=options) 141 | return r.payload -------------------------------------------------------------------------------- /kepconfig/adv_tags/min_tags.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Note: The code within this file was created in total or in part 8 | # with the use of AI tools. 9 | 10 | r"""`minimum_tags` exposes an API to allow modifications (add, delete, modify) to 11 | minimum tag objects within the Kepware Configuration API 12 | """ 13 | 14 | from ..connection import server 15 | from ..error import KepError, KepHTTPError 16 | from ..utils import _url_parse_object 17 | from typing import Union 18 | from .. import adv_tags 19 | 20 | MINIMUM_TAGS_ROOT = '/minimum_tags' 21 | 22 | def _get_minimum_tags_url(tag: str = None) -> str: 23 | '''Creates url object for the "minimum_tags" branch of Kepware's project tree. 24 | 25 | Returns the minimum tag specific url when a value is passed as the tag name. 26 | ''' 27 | if tag is None: 28 | return MINIMUM_TAGS_ROOT 29 | else: 30 | return f'{MINIMUM_TAGS_ROOT}/{_url_parse_object(tag)}' 31 | 32 | def add_minimum_tag(server: server, adv_tag_group_path: str, DATA: Union[dict, list]) -> Union[bool, list]: 33 | '''Add `"minimum_tag"` or multiple `"minimum_tag"` objects to a specific path in Kepware. 34 | Can be used to pass a list of minimum tags to be added at one path location. 35 | 36 | :param server: instance of the `server` class 37 | :param adv_tag_group_path: path identifying where to add minimum tag(s). Standard Kepware address decimal 38 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 39 | :param DATA: Dict or List of Dicts of the minimum tag(s) to add 40 | 41 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 42 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 43 | minimum tags added that failed. 44 | 45 | :raises KepHTTPError: If urllib provides an HTTPError 46 | :raises KepURLError: If urllib provides an URLError 47 | ''' 48 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 49 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_minimum_tags_url() 50 | 51 | r = server._config_add(url, DATA) 52 | if r.code == 201: 53 | return True 54 | elif r.code == 207: 55 | errors = [item for item in r.payload if item['code'] != 201] 56 | return errors 57 | else: 58 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 59 | 60 | def modify_minimum_tag(server: server, min_tag_path: str, DATA: dict, force: bool = False) -> bool: 61 | '''Modify a `"minimum_tag"` object and its properties in Kepware. 62 | 63 | :param server: instance of the `server` class 64 | :param min_tag_path: path identifying location and minimum tag to modify. Standard Kepware address decimal 65 | notation string including the minimum tag such as "_advancedtags.AdvTagGroup1.MinTag1" 66 | :param DATA: Dict of the `minimum_tag` properties to be modified 67 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 68 | 69 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 70 | 71 | :raises KepHTTPError: If urllib provides an HTTPError 72 | :raises KepURLError: If urllib provides an URLError 73 | ''' 74 | min_tag_data = server._force_update_check(force, DATA) 75 | path_obj = adv_tags._adv_tag_path_split(min_tag_path, isItem=True) 76 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_minimum_tags_url(path_obj['item']) 77 | 78 | r = server._config_update(url, min_tag_data) 79 | if r.code == 200: 80 | return True 81 | else: 82 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 83 | 84 | def del_minimum_tag(server: server, min_tag_path: str) -> bool: 85 | '''Delete `"minimum_tag"` object at a specific path in Kepware. 86 | 87 | :param server: instance of the `server` class 88 | :param min_tag_path: path identifying location and minimum tag to delete. Standard Kepware address decimal 89 | notation string including the minimum tag such as "_advancedtags.AdvTagGroup1.MinTag1" 90 | 91 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 92 | 93 | :raises KepHTTPError: If urllib provides an HTTPError 94 | :raises KepURLError: If urllib provides an URLError 95 | ''' 96 | path_obj = adv_tags._adv_tag_path_split(min_tag_path, isItem=True) 97 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_minimum_tags_url(path_obj['item']) 98 | 99 | r = server._config_del(url) 100 | if r.code == 200: 101 | return True 102 | else: 103 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 104 | 105 | def get_minimum_tag(server: server, min_tag_path: str) -> dict: 106 | '''Returns the properties of the `"minimum_tag"` object at a specific path in Kepware. 107 | 108 | :param server: instance of the `server` class 109 | :param min_tag_path: path identifying location and minimum tag to retrieve. Standard Kepware address decimal 110 | notation string including the minimum tag such as "_advancedtags.AdvTagGroup1.MinTag1" 111 | 112 | :return: Dict of data for the minimum tag requested 113 | 114 | :raises KepHTTPError: If urllib provides an HTTPError 115 | :raises KepURLError: If urllib provides an URLError 116 | ''' 117 | path_obj = adv_tags._adv_tag_path_split(min_tag_path, isItem=True) 118 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_minimum_tags_url(path_obj['item']) 119 | 120 | r = server._config_get(url) 121 | return r.payload 122 | 123 | def get_all_minimum_tags(server: server, adv_tag_group_path: str, *, options: dict = None) -> list: 124 | '''Returns the properties of all `"minimum_tag"` objects at a specific path in Kepware. 125 | 126 | :param server: instance of the `server` class 127 | :param adv_tag_group_path: path identifying location to retrieve minimum tag list. Standard Kepware address decimal 128 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 129 | :param options: *(optional)* Dict of parameters to filter, sort or paginate the list of minimum tags. Options are `filter`, 130 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 131 | 132 | :return: List of data for all minimum tags 133 | 134 | :raises KepHTTPError: If urllib provides an HTTPError 135 | :raises KepURLError: If urllib provides an URLError 136 | ''' 137 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 138 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_minimum_tags_url() 139 | 140 | r = server._config_get(url, params=options) 141 | return r.payload -------------------------------------------------------------------------------- /kepconfig/adv_tags/complex_tags.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Note: The code within this file was created in total or in part 8 | # with the use of AI tools. 9 | 10 | r"""`complex_tags` exposes an API to allow modifications (add, delete, modify) to 11 | complex tag objects within the Kepware Configuration API 12 | """ 13 | 14 | from ..connection import server 15 | from ..error import KepError, KepHTTPError 16 | from ..utils import _url_parse_object 17 | from typing import Union 18 | from .. import adv_tags 19 | 20 | COMPLEX_TAGS_ROOT = '/complex_tags' 21 | 22 | def _get_complex_tags_url(tag: str = None) -> str: 23 | '''Creates url object for the "complex_tags" branch of Kepware's project tree. 24 | 25 | Returns the complex tag specific url when a value is passed as the tag name. 26 | ''' 27 | if tag is None: 28 | return COMPLEX_TAGS_ROOT 29 | else: 30 | return f'{COMPLEX_TAGS_ROOT}/{_url_parse_object(tag)}' 31 | 32 | def add_complex_tag(server: server, adv_tag_group_path: str, DATA: Union[dict, list]) -> Union[bool, list]: 33 | '''Add `"complex_tag"` or multiple `"complex_tag"` objects to a specific path in Kepware. 34 | Can be used to pass a list of complex tags to be added at one path location. 35 | 36 | :param server: instance of the `server` class 37 | :param adv_tag_group_path: path identifying where to add complex tag(s). Standard Kepware address decimal 38 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 39 | :param DATA: Dict or List of Dicts of the complex tag(s) to add 40 | 41 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 42 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 43 | complex tags added that failed. 44 | 45 | :raises KepHTTPError: If urllib provides an HTTPError 46 | :raises KepURLError: If urllib provides an URLError 47 | ''' 48 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 49 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_complex_tags_url() 50 | 51 | r = server._config_add(url, DATA) 52 | if r.code == 201: 53 | return True 54 | elif r.code == 207: 55 | errors = [item for item in r.payload if item['code'] != 201] 56 | return errors 57 | else: 58 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 59 | 60 | def modify_complex_tag(server: server, complex_tag_path: str, DATA: dict, force: bool = False) -> bool: 61 | '''Modify a `"complex_tag"` object and its properties in Kepware. 62 | 63 | :param server: instance of the `server` class 64 | :param complex_tag_path: path identifying location and complex tag to modify. Standard Kepware address decimal 65 | notation string including the complex tag such as "_advancedtags.AdvTagGroup1.ComplexTag1" 66 | :param DATA: Dict of the `complex_tag` properties to be modified 67 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 68 | 69 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 70 | 71 | :raises KepHTTPError: If urllib provides an HTTPError 72 | :raises KepURLError: If urllib provides an URLError 73 | ''' 74 | complex_tag_data = server._force_update_check(force, DATA) 75 | path_obj = adv_tags._adv_tag_path_split(complex_tag_path, isItem=True) 76 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_complex_tags_url(path_obj['item']) 77 | 78 | r = server._config_update(url, complex_tag_data) 79 | if r.code == 200: 80 | return True 81 | else: 82 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 83 | 84 | def del_complex_tag(server: server, complex_tag_path: str) -> bool: 85 | '''Delete `"complex_tag"` object at a specific path in Kepware. 86 | 87 | :param server: instance of the `server` class 88 | :param complex_tag_path: path identifying location and complex tag to delete. Standard Kepware address decimal 89 | notation string including the complex tag such as "_advancedtags.AdvTagGroup1.ComplexTag1" 90 | 91 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 92 | 93 | :raises KepHTTPError: If urllib provides an HTTPError 94 | :raises KepURLError: If urllib provides an URLError 95 | ''' 96 | path_obj = adv_tags._adv_tag_path_split(complex_tag_path, isItem=True) 97 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_complex_tags_url(path_obj['item']) 98 | 99 | r = server._config_del(url) 100 | if r.code == 200: 101 | return True 102 | else: 103 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 104 | 105 | def get_complex_tag(server: server, complex_tag_path: str) -> dict: 106 | '''Returns the properties of the `"complex_tag"` object at a specific path in Kepware. 107 | 108 | :param server: instance of the `server` class 109 | :param complex_tag_path: path identifying location and complex tag to retrieve. Standard Kepware address decimal 110 | notation string including the complex tag such as "_advancedtags.AdvTagGroup1.ComplexTag1" 111 | 112 | :return: Dict of data for the complex tag requested 113 | 114 | :raises KepHTTPError: If urllib provides an HTTPError 115 | :raises KepURLError: If urllib provides an URLError 116 | ''' 117 | path_obj = adv_tags._adv_tag_path_split(complex_tag_path, isItem=True) 118 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_complex_tags_url(path_obj['item']) 119 | 120 | r = server._config_get(url) 121 | return r.payload 122 | 123 | def get_all_complex_tags(server: server, adv_tag_group_path: str, *, options: dict = None) -> list: 124 | '''Returns the properties of all `"complex_tag"` objects at a specific path in Kepware. 125 | 126 | :param server: instance of the `server` class 127 | :param adv_tag_group_path: path identifying location to retrieve complex tag list. Standard Kepware address decimal 128 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 129 | :param options: *(optional)* Dict of parameters to filter, sort or paginate the list of complex tags. Options are `filter`, 130 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 131 | 132 | :return: List of data for all complex tags 133 | 134 | :raises KepHTTPError: If urllib provides an HTTPError 135 | :raises KepURLError: If urllib provides an URLError 136 | ''' 137 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 138 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_complex_tags_url() 139 | 140 | r = server._config_get(url, params=options) 141 | return r.payload -------------------------------------------------------------------------------- /kepconfig/adv_tags/derived_tags.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Note: The code within this file was created in total or in part 8 | # with the use of AI tools. 9 | 10 | r"""`derived_tags` exposes an API to allow modifications (add, delete, modify) to 11 | derived tag objects within the Kepware Configuration API 12 | """ 13 | 14 | from ..connection import server 15 | from ..error import KepError, KepHTTPError 16 | from ..utils import _url_parse_object 17 | from typing import Union 18 | from .. import adv_tags 19 | 20 | DERIVED_TAGS_ROOT = '/derived_tags' 21 | 22 | def _get_derived_tags_url(tag: str = None) -> str: 23 | '''Creates url object for the "derived_tags" branch of Kepware's project tree. 24 | 25 | Returns the derived tag specific url when a value is passed as the tag name. 26 | ''' 27 | if tag is None: 28 | return DERIVED_TAGS_ROOT 29 | else: 30 | return f'{DERIVED_TAGS_ROOT}/{_url_parse_object(tag)}' 31 | 32 | def add_derived_tag(server: server, adv_tag_group_path: str, DATA: Union[dict, list]) -> Union[bool, list]: 33 | '''Add `"derived_tag"` or multiple `"derived_tag"` objects to a specific path in Kepware. 34 | Can be used to pass a list of derived tags to be added at one path location. 35 | 36 | :param server: instance of the `server` class 37 | :param adv_tag_group_path: path identifying where to add derived tag(s). Standard Kepware address decimal 38 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 39 | :param DATA: Dict or List of Dicts of the derived tag(s) to add 40 | 41 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 42 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 43 | derived tags added that failed. 44 | 45 | :raises KepHTTPError: If urllib provides an HTTPError 46 | :raises KepURLError: If urllib provides an URLError 47 | ''' 48 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 49 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_derived_tags_url() 50 | 51 | r = server._config_add(url, DATA) 52 | if r.code == 201: 53 | return True 54 | elif r.code == 207: 55 | errors = [item for item in r.payload if item['code'] != 201] 56 | return errors 57 | else: 58 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 59 | 60 | def modify_derived_tag(server: server, derived_tag_path: str, DATA: dict, force: bool = False) -> bool: 61 | '''Modify a `"derived_tag"` object and its properties in Kepware. 62 | 63 | :param server: instance of the `server` class 64 | :param derived_tag_path: path identifying location and derived tag to modify. Standard Kepware address decimal 65 | notation string including the derived tag such as "_advancedtags.AdvTagGroup1.DerivedTag1" 66 | :param DATA: Dict of the `derived_tag` properties to be modified 67 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 68 | 69 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 70 | 71 | :raises KepHTTPError: If urllib provides an HTTPError 72 | :raises KepURLError: If urllib provides an URLError 73 | ''' 74 | derived_tag_data = server._force_update_check(force, DATA) 75 | path_obj = adv_tags._adv_tag_path_split(derived_tag_path, isItem=True) 76 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_derived_tags_url(path_obj['item']) 77 | 78 | r = server._config_update(url, derived_tag_data) 79 | if r.code == 200: 80 | return True 81 | else: 82 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 83 | 84 | def del_derived_tag(server: server, derived_tag_path: str) -> bool: 85 | '''Delete `"derived_tag"` object at a specific path in Kepware. 86 | 87 | :param server: instance of the `server` class 88 | :param derived_tag_path: path identifying location and derived tag to delete. Standard Kepware address decimal 89 | notation string including the derived tag such as "_advancedtags.AdvTagGroup1.DerivedTag1" 90 | 91 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 92 | 93 | :raises KepHTTPError: If urllib provides an HTTPError 94 | :raises KepURLError: If urllib provides an URLError 95 | ''' 96 | path_obj = adv_tags._adv_tag_path_split(derived_tag_path, isItem=True) 97 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_derived_tags_url(path_obj['item']) 98 | 99 | r = server._config_del(url) 100 | if r.code == 200: 101 | return True 102 | else: 103 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 104 | 105 | def get_derived_tag(server: server, derived_tag_path: str) -> dict: 106 | '''Returns the properties of the `"derived_tag"` object at a specific path in Kepware. 107 | 108 | :param server: instance of the `server` class 109 | :param derived_tag_path: path identifying location and derived tag to retrieve. Standard Kepware address decimal 110 | notation string including the derived tag such as "_advancedtags.AdvTagGroup1.DerivedTag1" 111 | 112 | :return: Dict of data for the derived tag requested 113 | 114 | :raises KepHTTPError: If urllib provides an HTTPError 115 | :raises KepURLError: If urllib provides an URLError 116 | ''' 117 | path_obj = adv_tags._adv_tag_path_split(derived_tag_path, isItem=True) 118 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_derived_tags_url(path_obj['item']) 119 | 120 | r = server._config_get(url) 121 | return r.payload 122 | 123 | def get_all_derived_tags(server: server, adv_tag_group_path: str, *, options: dict = None) -> list: 124 | '''Returns the properties of all `"derived_tag"` objects at a specific path in Kepware. 125 | 126 | :param server: instance of the `server` class 127 | :param adv_tag_group_path: path identifying location to retrieve derived tag list. Standard Kepware address decimal 128 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 129 | :param options: *(optional)* Dict of parameters to filter, sort or paginate the list of derived tags. Options are `filter`, 130 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 131 | 132 | :return: List of data for all derived tags 133 | 134 | :raises KepHTTPError: If urllib provides an HTTPError 135 | :raises KepURLError: If urllib provides an URLError 136 | ''' 137 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 138 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_derived_tags_url() 139 | 140 | r = server._config_get(url, params=options) 141 | return r.payload -------------------------------------------------------------------------------- /kepconfig/adv_tags/average_tags.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Note: The code within this file was created in total or in part 8 | # with the use of AI tools. 9 | 10 | r"""`average_tags` exposes an API to allow modifications (add, delete, modify) to 11 | average tag objects within the Kepware Configuration API 12 | """ 13 | 14 | from ..connection import server 15 | from ..error import KepError, KepHTTPError 16 | from ..utils import _url_parse_object 17 | from typing import Union 18 | from .. import adv_tags 19 | 20 | AVERAGE_TAGS_ROOT = '/average_tags' 21 | 22 | def _get_average_tags_url(tag: str = None) -> str: 23 | '''Creates url object for the "average_tags" branch of Kepware's project tree. 24 | 25 | Returns the average tag specific url when a value is passed as the tag name. 26 | ''' 27 | if tag is None: 28 | return AVERAGE_TAGS_ROOT 29 | else: 30 | return f'{AVERAGE_TAGS_ROOT}/{_url_parse_object(tag)}' 31 | 32 | def add_average_tag(server: server, adv_tag_group_path: str, DATA: Union[dict, list]) -> Union[bool, list]: 33 | '''Add `"average_tag"` or multiple `"average_tag"` objects to a specific path in Kepware. 34 | Can be used to pass a list of average tags to be added at one path location. 35 | 36 | :param server: instance of the `server` class 37 | :param adv_tag_group_path: path identifying where to add average tag(s). Standard Kepware address decimal 38 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 39 | :param DATA: Dict or List of Dicts of the average tag(s) to add 40 | 41 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 42 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 43 | average tags added that failed. 44 | 45 | :raises KepHTTPError: If urllib provides an HTTPError 46 | :raises KepURLError: If urllib provides an URLError 47 | ''' 48 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 49 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_average_tags_url() 50 | 51 | r = server._config_add(url, DATA) 52 | if r.code == 201: 53 | return True 54 | elif r.code == 207: 55 | errors = [] 56 | for item in r.payload: 57 | if item['code'] != 201: 58 | errors.append(item) 59 | return errors 60 | else: 61 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 62 | 63 | def modify_average_tag(server: server, avg_tag_path: str, DATA: dict, force: bool = False) -> bool: 64 | '''Modify an `"average_tag"` object and its properties in Kepware. 65 | 66 | :param server: instance of the `server` class 67 | :param avg_tag_path: path identifying location and average tag to modify. Standard Kepware address decimal 68 | notation string including the average tag such as "_advancedtags.AdvTagGroup1.AvgTag1" 69 | :param DATA: Dict of the `average_tag` properties to be modified 70 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 71 | 72 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 73 | 74 | :raises KepHTTPError: If urllib provides an HTTPError 75 | :raises KepURLError: If urllib provides an URLError 76 | ''' 77 | avg_tag_data = server._force_update_check(force, DATA) 78 | path_obj = adv_tags._adv_tag_path_split(avg_tag_path, isItem=True) 79 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_average_tags_url(path_obj['item']) 80 | 81 | r = server._config_update(url, avg_tag_data) 82 | if r.code == 200: 83 | return True 84 | else: 85 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 86 | 87 | def del_average_tag(server: server, avg_tag_path: str) -> bool: 88 | '''Delete `"average_tag"` object at a specific path in Kepware. 89 | 90 | :param server: instance of the `server` class 91 | :param avg_tag_path: path identifying location and average tag to delete. Standard Kepware address decimal 92 | notation string including the average tag such as "_advancedtags.AdvTagGroup1.AvgTag1" 93 | 94 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 95 | 96 | :raises KepHTTPError: If urllib provides an HTTPError 97 | :raises KepURLError: If urllib provides an URLError 98 | ''' 99 | path_obj = adv_tags._adv_tag_path_split(avg_tag_path, isItem=True) 100 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_average_tags_url(path_obj['item']) 101 | 102 | r = server._config_del(url) 103 | if r.code == 200: 104 | return True 105 | else: 106 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 107 | 108 | def get_average_tag(server: server, avg_tag_path: str) -> dict: 109 | '''Returns the properties of the `"average_tag"` object at a specific path in Kepware. 110 | 111 | :param server: instance of the `server` class 112 | :param avg_tag_path: path identifying location and average tag to retrieve. Standard Kepware address decimal 113 | notation string including the average tag such as "_advancedtags.AdvTagGroup1.AvgTag1" 114 | 115 | :return: Dict of data for the average tag requested 116 | 117 | :raises KepHTTPError: If urllib provides an HTTPError 118 | :raises KepURLError: If urllib provides an URLError 119 | ''' 120 | path_obj = adv_tags._adv_tag_path_split(avg_tag_path, isItem=True) 121 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_average_tags_url(path_obj['item']) 122 | 123 | r = server._config_get(url) 124 | return r.payload 125 | 126 | def get_all_average_tags(server: server, adv_tag_group_path: str, *, options: dict = None) -> list: 127 | '''Returns the properties of all `"average_tag"` objects at a specific path in Kepware. 128 | 129 | :param server: instance of the `server` class 130 | :param adv_tag_group_path: path identifying location to retrieve average tag list. Standard Kepware address decimal 131 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 132 | :param options: *(optional)* Dict of parameters to filter, sort or paginate the list of average tags. Options are `filter`, 133 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 134 | 135 | :return: List of data for all average tags 136 | 137 | :raises KepHTTPError: If urllib provides an HTTPError 138 | :raises KepURLError: If urllib provides an URLError 139 | ''' 140 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 141 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_average_tags_url() 142 | 143 | r = server._config_get(url, params=options) 144 | return r.payload -------------------------------------------------------------------------------- /kepconfig/admin/lls.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c), PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`lls` exposes an API to allow modifications to Local License Server parameters in 8 | the Kepware Administration through the Kepware Configuration API 9 | """ 10 | from .. import connection 11 | from typing import Union 12 | from ..error import KepHTTPError, KepError 13 | 14 | 15 | 16 | LLS_ROOT = '/admin' 17 | FORCE_CHECK_URL = '/project/services/ForceLicenseCheck' 18 | LICENSING_SERVER_PORT = "libadminsettings.LICENSING_SERVER_PORT" 19 | LICENSING_SERVER_NAME = "libadminsettings.LICENSING_SERVER_NAME" 20 | LICENSING_SERVER_ENABLE = "libadminsettings.LICENSING_SERVER_ENABLE" 21 | LICENSING_CHECK_PERIOD_MINS = "libadminsettings.LICENSING_CHECK_PERIOD_MINS" 22 | LICENSING_SERVER_SSL_PORT = "libadminsettings.LICENSING_SERVER_SSL_PORT" 23 | LICENSING_SERVER_ALLOW_INSECURE_COMMS = "libadminsettings.LICENSING_SERVER_ALLOW_INSECURE_COMMS" 24 | LICENSING_SERVER_ALLOW_SELF_SIGNED_CERTS = "libadminsettings.LICENSING_SERVER_ALLOW_SELF_SIGNED_CERTS" 25 | LICENSING_CLIENT_ALIAS = "libadminsettings.LICENSING_CLIENT_ALIAS" 26 | 27 | class lls_config: 28 | '''A class to represent a admin properties for the Local License Server connection from an instance of Kepware. 29 | This object is used to easily manage the LLS parameters for a Kepware instance. 30 | 31 | :param server_name: Host name or IP address of the LLS server 32 | :param server_port: HTTP/non-SSL port to target for the LLS server 33 | :param check_period: Period that Kepware checks licensing status 34 | :param server_port_SSL: HTTPS/SSL port to target for the LLS server 35 | :param allow_insecure_comms: When True, use HTTP/non-SSL connection to LLS 36 | :param allow_self_signed_certs: Allow for self signed certificates to be used during HTTPS/SSL connections to the LLS 37 | :param instance_alias_name: Alias name for LLS to use as reference to this Kepware instance 38 | ''' 39 | 40 | def __init__(self, config = {}): 41 | self.server_name = config[LICENSING_SERVER_NAME] if LICENSING_SERVER_NAME in config else '' 42 | self.server_port = config[LICENSING_SERVER_PORT] if LICENSING_SERVER_PORT in config else 7070 43 | self.check_period = config[LICENSING_CHECK_PERIOD_MINS] if LICENSING_CHECK_PERIOD_MINS in config else 5 44 | self.server_port_SSL = config[LICENSING_SERVER_SSL_PORT] if LICENSING_SERVER_SSL_PORT in config else 1883 45 | self.allow_insecure_comms = config[LICENSING_SERVER_ALLOW_INSECURE_COMMS] if LICENSING_SERVER_ALLOW_INSECURE_COMMS in config else False 46 | self.allow_self_signed_certs = config[LICENSING_SERVER_ALLOW_SELF_SIGNED_CERTS] if LICENSING_SERVER_ALLOW_SELF_SIGNED_CERTS in config else False 47 | self.instance_alias_name = config[LICENSING_CLIENT_ALIAS] if LICENSING_CLIENT_ALIAS in config else '' 48 | 49 | def _get_dict(self): 50 | return { 51 | LICENSING_SERVER_PORT: self.server_port, 52 | LICENSING_SERVER_NAME: self.server_name, 53 | LICENSING_CHECK_PERIOD_MINS: self.check_period, 54 | LICENSING_SERVER_SSL_PORT: self.server_port_SSL, 55 | LICENSING_SERVER_ALLOW_INSECURE_COMMS: self.allow_insecure_comms, 56 | LICENSING_SERVER_ALLOW_SELF_SIGNED_CERTS: self.allow_self_signed_certs, 57 | LICENSING_CLIENT_ALIAS: self.instance_alias_name 58 | } 59 | 60 | def __str__(self) -> str: 61 | return "{}".format(self._get_dict()) 62 | 63 | def get_lls_config(server: connection.server) -> lls_config: 64 | '''Returns the properties of the Local License server connection properties. Returned object is `lls_config` class object. 65 | 66 | :param server: instance of the `server` class 67 | 68 | :return: `lls_config` class object with lls connection configuration 69 | 70 | :raises KepHTTPError: If urllib provides an HTTPError 71 | :raises KepURLError: If urllib provides an URLError 72 | ''' 73 | 74 | r = server._config_get(server.url + LLS_ROOT) 75 | return lls_config(r.payload) 76 | 77 | def update_lls_config(server: connection.server, config: lls_config) -> bool: 78 | '''Updates the Local License Server connection properties for Kepware. 79 | 80 | :param server: instance of the `server` class 81 | :param config: `lls_config` class object with lls connection configuration 82 | 83 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 84 | 85 | :raises KepHTTPError: If urllib provides an HTTPError 86 | :raises KepURLError: If urllib provides an URLError 87 | ''' 88 | 89 | DATA = config._get_dict() 90 | r = server._config_update(server.url + LLS_ROOT, DATA) 91 | if r.code == 200: return True 92 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 93 | 94 | def enable_lls(server: connection.server) -> bool: 95 | '''Enables the Local License Server connection for Kepware. 96 | 97 | :param server: instance of the `server` class 98 | 99 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 100 | 101 | :raises KepHTTPError: If urllib provides an HTTPError 102 | :raises KepURLError: If urllib provides an URLError 103 | ''' 104 | 105 | r = server._config_update(server.url + LLS_ROOT, {LICENSING_SERVER_ENABLE: True}) 106 | if r.code == 200: return True 107 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 108 | 109 | def disable_lls(server: connection.server) -> bool: 110 | '''Disables the Local License Server connection for Kepware. 111 | 112 | :param server: instance of the `server` class 113 | 114 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 115 | 116 | :raises KepHTTPError: If urllib provides an HTTPError 117 | :raises KepURLError: If urllib provides an URLError 118 | ''' 119 | 120 | r = server._config_update(server.url + LLS_ROOT, {LICENSING_SERVER_ENABLE: False}) 121 | if r.code == 200: return True 122 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 123 | 124 | def force_license_check(server: connection.server, job_ttl: int = None): 125 | '''Executes a ForceLicenseCheck service call to the Kepware instance. This triggers the server to verify the 126 | license state of the license received from the Local License Server. 127 | 128 | :param server: instance of the `server` class 129 | :param job_ttl: *(optional)* Determines the number of seconds a job instance will exist following completion. 130 | 131 | :return: `KepServiceResponse` instance with job information 132 | 133 | :raises KepHTTPError: If urllib provides an HTTPError (If not HTTP code 202 [Accepted] or 429 [Too Busy] returned) 134 | :raises KepURLError: If urllib provides an URLError 135 | ''' 136 | 137 | url = f'{server.url}{FORCE_CHECK_URL}' 138 | job = server._kep_service_execute(url, None, job_ttl) 139 | return job -------------------------------------------------------------------------------- /kepconfig/adv_tags/cumulative_tags.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # Note: The code within this file was created in total or in part 8 | # with the use of AI tools. 9 | 10 | r"""`cumulative_tags` exposes an API to allow modifications (add, delete, modify) to 11 | cumulative tag objects within the Kepware Configuration API 12 | """ 13 | 14 | from ..connection import server 15 | from ..error import KepError, KepHTTPError 16 | from ..utils import _url_parse_object 17 | from typing import Union 18 | from .. import adv_tags 19 | 20 | CUMULATIVE_TAGS_ROOT = '/cumulative_tags' 21 | 22 | def _get_cumulative_tags_url(tag: str = None) -> str: 23 | '''Creates url object for the "cumulative_tags" branch of Kepware's project tree. 24 | 25 | Returns the cumulative tag specific url when a value is passed as the tag name. 26 | ''' 27 | if tag is None: 28 | return CUMULATIVE_TAGS_ROOT 29 | else: 30 | return f'{CUMULATIVE_TAGS_ROOT}/{_url_parse_object(tag)}' 31 | 32 | def add_cumulative_tag(server: server, adv_tag_group_path: str, DATA: Union[dict, list]) -> Union[bool, list]: 33 | '''Add `"cumulative_tag"` or multiple `"cumulative_tag"` objects to a specific path in Kepware. 34 | Can be used to pass a list of cumulative tags to be added at one path location. 35 | 36 | :param server: instance of the `server` class 37 | :param adv_tag_group_path: path identifying where to add cumulative tag(s). Standard Kepware address decimal 38 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 39 | :param DATA: Dict or List of Dicts of the cumulative tag(s) to add 40 | 41 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 42 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 43 | cumulative tags added that failed. 44 | 45 | :raises KepHTTPError: If urllib provides an HTTPError 46 | :raises KepURLError: If urllib provides an URLError 47 | ''' 48 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 49 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_cumulative_tags_url() 50 | 51 | r = server._config_add(url, DATA) 52 | if r.code == 201: 53 | return True 54 | elif r.code == 207: 55 | errors = [item for item in r.payload if item['code'] != 201] 56 | return errors 57 | else: 58 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 59 | 60 | def modify_cumulative_tag(server: server, cumulative_tag_path: str, DATA: dict, force: bool = False) -> bool: 61 | '''Modify a `"cumulative_tag"` object and its properties in Kepware. 62 | 63 | :param server: instance of the `server` class 64 | :param cumulative_tag_path: path identifying location and cumulative tag to modify. Standard Kepware address decimal 65 | notation string including the cumulative tag such as "_advancedtags.AdvTagGroup1.CumulativeTag1" 66 | :param DATA: Dict of the `cumulative_tag` properties to be modified 67 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 68 | 69 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 70 | 71 | :raises KepHTTPError: If urllib provides an HTTPError 72 | :raises KepURLError: If urllib provides an URLError 73 | ''' 74 | cum_tag_data = server._force_update_check(force, DATA) 75 | path_obj = adv_tags._adv_tag_path_split(cumulative_tag_path, isItem=True) 76 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_cumulative_tags_url(path_obj['item']) 77 | 78 | r = server._config_update(url, cum_tag_data) 79 | if r.code == 200: 80 | return True 81 | else: 82 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 83 | 84 | def del_cumulative_tag(server: server, cumulative_tag_path: str) -> bool: 85 | '''Delete `"cumulative_tag"` object at a specific path in Kepware. 86 | 87 | :param server: instance of the `server` class 88 | :param cumulative_tag_path: path identifying location and cumulative tag to delete. Standard Kepware address decimal 89 | notation string including the cumulative tag such as "_advancedtags.AdvTagGroup1.CumulativeTag1" 90 | 91 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 92 | 93 | :raises KepHTTPError: If urllib provides an HTTPError 94 | :raises KepURLError: If urllib provides an URLError 95 | ''' 96 | path_obj = adv_tags._adv_tag_path_split(cumulative_tag_path, isItem=True) 97 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_cumulative_tags_url(path_obj['item']) 98 | 99 | r = server._config_del(url) 100 | if r.code == 200: 101 | return True 102 | else: 103 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 104 | 105 | def get_cumulative_tag(server: server, cumulative_tag_path: str) -> dict: 106 | '''Returns the properties of the `"cumulative_tag"` object at a specific path in Kepware. 107 | 108 | :param server: instance of the `server` class 109 | :param cumulative_tag_path: path identifying location and cumulative tag to retrieve. Standard Kepware address decimal 110 | notation string including the cumulative tag such as "_advancedtags.AdvTagGroup1.CumulativeTag1" 111 | 112 | :return: Dict of data for the cumulative tag requested 113 | 114 | :raises KepHTTPError: If urllib provides an HTTPError 115 | :raises KepURLError: If urllib provides an URLError 116 | ''' 117 | path_obj = adv_tags._adv_tag_path_split(cumulative_tag_path, isItem=True) 118 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_cumulative_tags_url(path_obj['item']) 119 | 120 | r = server._config_get(url) 121 | return r.payload 122 | 123 | def get_all_cumulative_tags(server: server, adv_tag_group_path: str, *, options: dict = None) -> list: 124 | '''Returns the properties of all `"cumulative_tag"` objects at a specific path in Kepware. 125 | 126 | :param server: instance of the `server` class 127 | :param adv_tag_group_path: path identifying location to retrieve cumulative tag list. Standard Kepware address decimal 128 | notation string such as "_advancedtags.AdvTagGroup1" or "_advancedtags.AdvTagGroup1.AdvTagGroupChild" 129 | :param options: *(optional)* Dict of parameters to filter, sort or paginate the list of cumulative tags. Options are `filter`, 130 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 131 | 132 | :return: List of data for all cumulative tags 133 | 134 | :raises KepHTTPError: If urllib provides an HTTPError 135 | :raises KepURLError: If urllib provides an URLError 136 | ''' 137 | path_obj = adv_tags._adv_tag_path_split(adv_tag_group_path, isItem=False) 138 | url = adv_tags._create_adv_tags_base_url(server.url, path_obj) + _get_cumulative_tags_url() 139 | 140 | r = server._config_get(url, params=options) 141 | return r.payload -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kepware Configuration API SDK for Python 2 | 3 | [![Released Version](https://img.shields.io/pypi/v/kepconfig)](https://pypi.org/project/kepconfig) [![Supported Versions](https://img.shields.io/pypi/pyversions/kepconfig)](https://pypi.org/project/kepconfig) ![PyPI - Downloads](https://img.shields.io/pypi/dm/kepconfig) ![PyPI - License](https://img.shields.io/pypi/l/kepconfig) 4 | 5 | This is a package to help create Python applications to conduct operations with the Kepware Configuration API. This package is designed to work with all versions of Kepware that support the Configuration API including Thingworx Kepware Server (TKS), Thingworx Kepware Edge (TKE) and KEPServerEX (KEP). For reference, Kepware Server in this documentation will refer to both TKS and KEP versions. 6 | 7 | **API reference documentation is available on [Github Pages](https://ptcinc.github.io/Kepware-ConfigAPI-SDK-Python)** 8 | 9 | ## Prerequisites 10 | 11 | Package supported and tested on Python 3.9 or later. Older versions support earlier Python 3 environments but have less functionality. All HTTP communication is handled by the [urllib](https://docs.python.org/3/library/urllib.html#module-urllib) Python standard library. 12 | 13 | ## Features 14 | 15 | - Supports both HTTP and HTTPS connections with certificate validation options 16 | 17 | Package allows for *GET*, *ADD*, *DELETE*, and *MODIFY* functions for the following Kepware configuration objects: 18 | 19 | | Features | TKS/KEP | TKE | 20 | | :----------: | :----------: | :----------: | 21 | | **Project Properties**
*(Get and Modify Only)* | Y | Y | 22 | | **Connectivity**
*(Channel, Devices, Tags, Tag Groups)* | Y | Y | 23 | | **IoT Gateway**
*(Agents, IoT Items)* | Y | Y | 24 | | **Datalogger**
*(Log Groups, Items, Mapping, Triggers, Reset Mapping Service)* | Y | N | 25 | | **UA Gateway**
*(Certificates, Server Endpoints, Client Connections, Server Interface parameters)* | Y*** | N | 26 | | **Advanced Tags**
*(All tag types and tag groups)*| Y****** | N | 27 | | **Administration**
*(User Groups, Users, UA Endpoints, Local License Server)* | Y* | Y | 28 | | **Product Info and Health Status\*\*** | Y | Y | 29 | | **Import Project (via JsonProjectLoad Service) / Export Project\*\*\*\***| Y | Y | 30 | | **Backup Project (via CreateBackup Service) / Export Project\*\*\*\*\***| Y | Y | 31 | 32 | - Note (*) - UA Endpoints and Local License Server supported for TKE only 33 | - Note (**) - Added to Kepware Server v6.13 / TKE v1.5 and later builds 34 | - Note (***) - TKS only v6.16 and later 35 | - Note (****) - Added to Kepware Server v6.17 / TKE v1.10 and later builds 36 | - Note (*****) - Added to Kepware Server v6.18 / TKE v1.11 and later builds 37 | 38 | Driver specific features: 39 | 40 | | Driver | Features | 41 | | :----------: | :----------: | 42 | |GE Ethernet Global Data|Exchanges, Ranges and Name Resolutions| 43 | |Universal Device Driver|Profile Library| 44 | 45 | Methods to read the following logs: 46 | 47 | | Logs | TKS/KEP | TKE | 48 | | :----------: | :----------: | :----------: | 49 | | **Event Log** | Y | Y | 50 | | **API Transaction Log** | Y | Y | 51 | | **Audit Log*** | Y | Y | 52 | 53 | - Note (*) - Implemented for Kepware Server v6.18+ and TKE 1.11+ 54 | 55 | Configuration API *Services* implemented: 56 | 57 | | Services | TKS/KEP | TKE | 58 | | :----------: | :----------: | :----------: | 59 | | **TagGeneration**
*(for supported drivers)* | Y | Y | 60 | | **ReinitializeRuntime** | Y* | Y | 61 | | **ProjectLoad and ProjectSave**| Y | Y | 62 | | **JsonProjectLoad\*\***
*(used for import project feature)*| Y | Y | 63 | | **CreateBackup\*\*\***| Y | Y | 64 | 65 | - Note (*) - Reinitialize service was implemented for Kepware Server v6.8+ 66 | - Note (**) - Added to Kepware Server v6.17 / TKE v1.10 and later builds 67 | - Note (***) - Added to Kepware Server v6.18 / TKE v1.11 and later builds 68 | 69 | Filtering, sorting and pagination query options are added for any collections methods (ex: `get_all_devices()` or `get_all_channel()`). 70 | 71 | Generic REST methods are provided to use for functions not developed in SDK package. These are found in the `Server` class in [connection.py](./kepconfig/connection.py) 72 | 73 | ## Known Limitations 74 | 75 | - Other property configuration for more complex drivers with objects besides channels, devices, tags and tag groups are not always explicitly defined 76 | - Other supported plug-ins (EFM Exporter, Scheduler, etc) are not defined 77 | - When using hostnames (not IP addresses) for connections, delays may occur under certain network configurations as the connection may attempt IPv6 connections first. IPv6 is not supported by Kepware servers at this time. 78 | 79 | ## Installation 80 | 81 | Package can be installed with `pip` using the following: 82 | 83 | ```cmd 84 | pip install kepconfig 85 | ``` 86 | 87 | ## Key Concepts 88 | 89 | **NOTE:** Detailed examples can also be found in the [examples](./examples/) folder. 90 | 91 | ### Create server connection instance 92 | 93 | ```python 94 | from kepconfig import connection 95 | 96 | server = connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '') 97 | 98 | # For HTTPS connections: 99 | server = connection.server(host = '127.0.0.1', port = 57512, user = 'Administrator', pw = '', https=True) 100 | 101 | ``` 102 | 103 | For certificate validation, the SDK uses the OS/systems trusted certificate store. The connection uses the `create_default_context()` function as part of urllib as described at the following links: 104 | 105 | - [ssl.create_default_context](https://docs.python.org/3/library/ssl.html#ssl.create_default_context) 106 | - [ssl.SSLContext.load_default_certs](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_default_certs) 107 | - [set_default_verify_paths](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.set_default_verify_paths) 108 | 109 | For Windows OSes, the Kepware Server's instance certificate can be loaded into the hosts "Trusted Root Certificate Authorities" store. 110 | 111 | ### Create an object 112 | 113 | Objects such as channels or devices can be created either singularly or with children included. 114 | 115 | ### Ex: Add Single channel 116 | 117 | ```python 118 | from kepconfig.connectivity import channel 119 | 120 | channel_data = {"common.ALLTYPES_NAME": "Channel1","servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Simulator"} 121 | result = channel.add_channel(server,channel_data) 122 | ``` 123 | 124 | ### Ex: Add Multiple tags 125 | 126 | ```python 127 | from kepconfig.connectivity import tag 128 | 129 | tag_info = [ 130 | { 131 | "common.ALLTYPES_NAME": "Temp", 132 | "servermain.TAG_ADDRESS": "R0" 133 | }, 134 | { 135 | "common.ALLTYPES_NAME": "Temp2", 136 | "servermain.TAG_ADDRESS": "R1" 137 | } 138 | ] 139 | tag_path = '{}.{}.{}'.format(ch_name, dev_name, tag_group_path) 140 | result = tag.add_tag(server, tag_path, tag_info) 141 | 142 | ``` 143 | 144 | ## Need More Information 145 | 146 | **Visit:** 147 | 148 | - [Kepconfig Package Documentation on Github Pages](https://ptcinc.github.io/Kepware-ConfigAPI-SDK-Python) 149 | - [Kepware.com](https://www.kepware.com/) 150 | - [PTC.com](https://www.ptc.com/) 151 | -------------------------------------------------------------------------------- /kepconfig/iot_gateway/iot_items.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | 8 | r"""`iot_items` exposes an API to allow modifications (add, delete, modify) to 9 | iot_items objects within the Kepware Configuration API 10 | """ 11 | 12 | from typing import Union 13 | from .. import utils 14 | from ..connection import server 15 | from .. import iot_gateway as IOT 16 | from ..error import KepError, KepHTTPError 17 | from ..utils import _url_parse_object 18 | 19 | IOT_ITEMS_ROOT = '/iot_items' 20 | 21 | def _create_url(tag = None): 22 | '''Creates url object for the "iot items" branch of Kepware's IoT Agents property model. Used 23 | to build a part of Kepware Configuration API URL structure 24 | 25 | Returns the device specific url when a value is passed as the iot item name. 26 | ''' 27 | if tag == None: 28 | return IOT_ITEMS_ROOT 29 | else: 30 | normalized_tag = utils._address_dedecimal(tag) 31 | return '{}/{}'.format(IOT_ITEMS_ROOT, _url_parse_object(normalized_tag)) 32 | 33 | 34 | def add_iot_item(server: server, DATA: Union[dict, list], agent: str, agent_type: str) -> Union[bool, list]: 35 | '''Add a `"iot item"` or multiple `"iot item"` objects to Kepware's IoT Gateway agent. Additionally 36 | it can be used to pass a list of iot items to be added to an agent all at once. 37 | 38 | :param server: instance of the `server` class 39 | :param DATA: Dict or List of Dicts of the iot item or list of items 40 | expected by Kepware Configuration API 41 | :param agent: name of IoT Agent 42 | :param agent_type: agent type. Valid values are `MQTT Client`, `REST Client` or `REST Server` 43 | 44 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 45 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 46 | iot items added that failed. 47 | 48 | :raises KepHTTPError: If urllib provides an HTTPError 49 | :raises KepURLError: If urllib provides an URLError 50 | ''' 51 | r = server._config_add(server.url + IOT.agent._create_url(agent_type, agent) + _create_url(), DATA) 52 | if r.code == 201: return True 53 | elif r.code == 207: 54 | errors = [] 55 | for item in r.payload: 56 | if item['code'] != 201: 57 | errors.append(item) 58 | return errors 59 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 60 | 61 | def del_iot_item(server: server, iot_item: str, agent: str, agent_type: str) -> bool: 62 | '''Delete an `"iot item"` object in Kepware. 63 | 64 | :param server: instance of the `server` class 65 | :param iot_item: IoT item to delete 66 | :param agent: name of IoT Agent 67 | :param agent_type: agent type. Valid values are `MQTT Client`, `REST Client` or `REST Server` 68 | 69 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 70 | 71 | :raises KepHTTPError: If urllib provides an HTTPError 72 | :raises KepURLError: If urllib provides an URLError 73 | ''' 74 | r = server._config_del(server.url + IOT.agent._create_url(agent_type, agent) + _create_url(iot_item)) 75 | if r.code == 200: return True 76 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 77 | 78 | def modify_iot_item(server: server, DATA: dict, agent: str, agent_type: str, *, iot_item: str = None, force: bool = False) -> bool: 79 | '''Modify an `"iot item"` object and it's properties in Kepware. If a `"iot item"` is not provided as an input, 80 | you need to identify the iot item in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 81 | assume that is the iot item that is to be modified. 82 | 83 | :param server: instance of the `server` class 84 | :param DATA: Dict of the iot item properties to be modified. 85 | :param agent: name of IoT Agent 86 | :param agent_type: agent type. Valid values are `MQTT Client`, `REST Client` or `REST Server` 87 | :param iot_item: *(optional)* name of IoT item to modify. Only needed if not existing in `"DATA"` 88 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 89 | 90 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 91 | 92 | :raises KepHTTPError: If urllib provides an HTTPError 93 | :raises KepURLError: If urllib provides an URLError 94 | ''' 95 | 96 | agent_data = server._force_update_check(force, DATA) 97 | if iot_item == None: 98 | try: 99 | r = server._config_update(server.url + IOT.agent._create_url(agent_type, agent) + _create_url(agent_data['common.ALLTYPES_NAME']), agent_data) 100 | if r.code == 200: return True 101 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 102 | except KeyError as err: 103 | err_msg = 'Error: No agent identified in DATA | Key Error: {}'.format(err) 104 | raise KepError(err_msg) 105 | else: 106 | r = server._config_update(server.url + IOT.agent._create_url(agent_type, agent) + _create_url(iot_item), agent_data) 107 | if r.code == 200: return True 108 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 109 | 110 | def get_iot_item(server: server, iot_item: str, agent: str, agent_type: str)-> dict: 111 | '''Returns the properties of the `"iot item"` object. 112 | 113 | :param server: instance of the `server` class 114 | :param iot_item: name of IoT item to retrieve properties 115 | :param agent: name of IoT Agent 116 | :param agent_type: agent type. Valid values are `MQTT Client`, `REST Client` or `REST Server` 117 | 118 | :return: Dict of properties for the iot item requested 119 | 120 | :raises KepHTTPError: If urllib provides an HTTPError 121 | :raises KepURLError: If urllib provides an URLError 122 | ''' 123 | r = server._config_get(server.url + IOT.agent._create_url(agent_type, agent) + _create_url(iot_item)) 124 | return r.payload 125 | 126 | def get_all_iot_items(server: server, agent: str, agent_type: str, *, options: dict = None) -> list: 127 | '''Returns the properties of all `"iot item"` objects for an agent. 128 | 129 | :param server: instance of the `server` class 130 | :param iot_item: name of IoT item to retrieve properties 131 | :param agent: name of IoT Agent 132 | :param agent_type: agent type. Valid values are `MQTT Client`, `REST Client` or `REST Server` 133 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of IoT items. Options are 'filter', 134 | 'sortOrder', 'sortProperty', 'pageNumber', and 'pageSize'. Only used when exchange_name is not defined. 135 | 136 | :return: list of properties for all IoT items 137 | 138 | :raises KepHTTPError: If urllib provides an HTTPError 139 | :raises KepURLError: If urllib provides an URLError 140 | ''' 141 | r = server._config_get(f'{server.url}{IOT.agent._create_url(agent_type, agent)}{_create_url()}', params= options) 142 | return r.payload -------------------------------------------------------------------------------- /kepconfig/connectivity/egd/range.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | 8 | r"""`ranges` exposes an API to allow modifications (add, delete, modify) to 9 | range objects in exchanges for EGD devices within the Kepware Configuration API 10 | """ 11 | 12 | from typing import Union 13 | from .. import egd as EGD 14 | from ...connection import server 15 | from ...error import KepError, KepHTTPError 16 | from ...utils import _url_parse_object 17 | 18 | RANGES_ROOT = '/ranges' 19 | 20 | def _create_url(device_path, ex_type, exchange_name, range = None): 21 | '''Creates url object for the "range" branch of Kepware's project tree. Used 22 | to build a part of Kepware Configuration API URL structure 23 | 24 | Returns the range specific url when a value is passed as the range name. 25 | ''' 26 | exchange_root = EGD.exchange._create_url(device_path, ex_type, exchange_name) 27 | 28 | if range == None: 29 | return '{}{}'.format(exchange_root, RANGES_ROOT) 30 | else: 31 | return '{}{}/{}'.format(exchange_root, RANGES_ROOT, _url_parse_object(range)) 32 | 33 | def add_range(server: server, device_path: str, ex_type: str, exchange_name: str, DATA: Union[dict, list]) -> Union[bool, list]: 34 | '''Add a `"range"` or multiple `"range"` objects to Kepware. This allows you to 35 | create a range or multiple ranges all in one function, if desired. 36 | 37 | When passing multiple ranges, they will be populated in the same order 38 | in the list sent. Ensure you provide the list in the order desired. 39 | 40 | :param server: instance of the `server` class 41 | :param device_path: path to EGD device. Standard Kepware address decimal 42 | notation string such as `"channel1.device1"` 43 | :param ex_type: type of exchange, either `CONSUMER` or `PRODUCER` 44 | :param exchange_name: name of exchange that range is located 45 | :param DATA: Dict or List of Dicts of the range(s) to add 46 | 47 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 48 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 49 | ranges added that failed. 50 | 51 | :raises KepHTTPError: If urllib provides an HTTPError 52 | :raises KepURLError: If urllib provides an URLError 53 | ''' 54 | 55 | r = server._config_add(server.url + _create_url(device_path, ex_type, exchange_name), DATA) 56 | if r.code == 201: return True 57 | elif r.code == 207: 58 | errors = [] 59 | for item in r.payload: 60 | if item['code'] != 201: 61 | errors.append(item) 62 | return errors 63 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 64 | 65 | def del_range(server: server, device_path: str, ex_type: str, exchange_name: str, range_name: str) -> bool: 66 | '''Delete a `"range"` object in Kepware. 67 | 68 | :param server: instance of the `server` class 69 | :param device_path: path to EGD device. Standard Kepware address decimal 70 | notation string such as `"channel1.device1"` 71 | :param ex_type: type of exchange, either `CONSUMER` or `PRODUCER` 72 | :param exchange_name: name of exchange that range is located 73 | :param range_name: name of range to delete 74 | 75 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 76 | 77 | :raises KepHTTPError: If urllib provides an HTTPError 78 | :raises KepURLError: If urllib provides an URLError 79 | ''' 80 | 81 | r = server._config_del(server.url + _create_url(device_path, ex_type, exchange_name, range_name)) 82 | if r.code == 200: return True 83 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 84 | 85 | def modify_range(server: server, device_path: str, ex_type: str, exchange_name: str, DATA: dict, *, range_name: str = None, force: bool = False) -> bool: 86 | '''Modify a `"range"` object and it's properties in Kepware. If a `"range_name"` is not provided as an input, 87 | you need to identify the range in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 88 | assume that is the range that is to be modified. 89 | 90 | :param server: instance of the `server` class 91 | :param device_path: path to EGD device. Standard Kepware address decimal 92 | notation string such as `"channel1.device1"` 93 | :param ex_type: type of exchange, either `CONSUMER` or `PRODUCER` 94 | :param exchange_name: name of exchange that range is located 95 | :param DATA: Dict of the range properties to be modified. 96 | :param range_name: *(optional)* name of range to to modify. Only needed if not existing in `"DATA"` 97 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 98 | 99 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 100 | 101 | :raises KepHTTPError: If urllib provides an HTTPError 102 | :raises KepURLError: If urllib provides an URLError 103 | ''' 104 | 105 | range_data = server._force_update_check(force, DATA) 106 | if range_name == None: 107 | try: 108 | r = server._config_update(server.url + _create_url(device_path, ex_type, exchange_name, range_data['common.ALLTYPES_NAME']), range_data) 109 | if r.code == 200: return True 110 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 111 | except KeyError as err: 112 | err_msg = 'Error: No range identified in DATA | Key Error: {}'.format(err) 113 | raise KepError(err_msg) 114 | else: 115 | r = server._config_update(server.url + _create_url(device_path, ex_type, exchange_name, range_name), range_data) 116 | if r.code == 200: return True 117 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 118 | 119 | def get_range(server: server, device_path: str, ex_type: str, exchange_name: str, range_name: str = None, *, options: dict = None) -> Union[dict, list]: 120 | '''Returns the properties of the `"range"` object or a list of all ranges. 121 | 122 | :param server: instance of the `server` class 123 | :param device_path: path to EGD device. Standard Kepware address decimal 124 | notation string such as `"channel1.device1"` 125 | :param ex_type: type of exchange, either `CONSUMER` or `PRODUCER` 126 | :param exchange_name: name of exchange that range is located 127 | :param DATA: Dict of the range properties to be modified. 128 | :param range_name: *(optional)* name of range to retrieve. If not defined, get all ranges 129 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of exchanges. Options are 'filter', 130 | 'sortOrder', 'sortProperty', 'pageNumber', and 'pageSize'. Only used when range_name is not defined. 131 | 132 | :return: Dict of properties for the range requested or a List of ranges and their properties 133 | 134 | :raises KepHTTPError: If urllib provides an HTTPError 135 | :raises KepURLError: If urllib provides an URLError 136 | ''' 137 | if range_name == None: 138 | r = server._config_get(f'{server.url}{_create_url(device_path, ex_type, exchange_name)}', params= options) 139 | else: 140 | r = server._config_get(f'{server.url}{_create_url(device_path, ex_type, exchange_name, range_name)}') 141 | return r.payload -------------------------------------------------------------------------------- /kepconfig/datalogger/log_group.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | r"""`log_group` exposes an API to allow modifications (add, delete, modify) to 8 | log group objects in DataLogger within the Kepware Configuration API 9 | """ 10 | from typing import Union 11 | from ..connection import KepServiceResponse, server 12 | from ..error import KepError, KepHTTPError 13 | from ..utils import _url_parse_object 14 | 15 | ENABLE_PROPERTY = 'datalogger.LOG_GROUP_ENABLED' 16 | LOG_GROUP_ROOT = '/project/_datalogger/log_groups' 17 | SERVICES_ROOT = '/services' 18 | def _create_url(log_group = None): 19 | '''Creates url object for the "log_group" branch of Kepware's project tree. Used 20 | to build a part of Kepware Configuration API URL structure 21 | 22 | Returns the agent specific url when a value is passed as the agent name. 23 | ''' 24 | 25 | if log_group == None: 26 | return '{}'.format(LOG_GROUP_ROOT) 27 | else: 28 | return '{}/{}'.format(LOG_GROUP_ROOT, _url_parse_object(log_group)) 29 | 30 | 31 | def add_log_group(server: server, DATA: Union[dict, list]) -> Union[bool, list]: 32 | '''Add a `"log group"` or multiple `"log groups"` objects to Kepware's DataLogger. It can be used 33 | to pass a list of log groups to be added all at once. 34 | 35 | :param server: instance of the `server` class 36 | :param DATA: Dict or a list of the log groups to add through Kepware Configuration API 37 | 38 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 39 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 40 | log groups added that failed. 41 | 42 | :raises KepHTTPError: If urllib provides an HTTPError 43 | :raises KepURLError: If urllib provides an URLError 44 | ''' 45 | 46 | r = server._config_add(server.url + _create_url(), DATA) 47 | if r.code == 201: return True 48 | elif r.code == 207: 49 | errors = [] 50 | for item in r.payload: 51 | if item['code'] != 201: 52 | errors.append(item) 53 | return errors 54 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 55 | 56 | def del_log_group(server: server, log_group: str) -> bool: 57 | '''Delete a `"log group"` object in Kepware's Datalogger. 58 | 59 | :param server: instance of the `server` class 60 | :param log_group: name of log group to delete 61 | 62 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 63 | 64 | :raises KepHTTPError: If urllib provides an HTTPError 65 | :raises KepURLError: If urllib provides an URLError 66 | ''' 67 | r = server._config_del(server.url + _create_url(log_group)) 68 | if r.code == 200: return True 69 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 70 | 71 | def modify_log_group(server: server, DATA: dict, *, log_group: str = None, force: bool = False) -> bool: 72 | '''Modify a `"log group"` object and it's properties in Kepware's Datalogger. If a `"log group"` is not provided as an input, 73 | you need to identify the log group in the *'common.ALLTYPES_NAME'* property field in the `"DATA"`. It will 74 | assume that is the log group that is to be modified. 75 | 76 | :param server: instance of the `server` class 77 | :param DATA: Dict of the log group properties to be modified. 78 | :param log_group: *(optional)* name of log group to modify. Only needed if not existing in `"DATA"` 79 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 80 | 81 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 82 | 83 | :raises KepHTTPError: If urllib provides an HTTPError 84 | :raises KepURLError: If urllib provides an URLError 85 | ''' 86 | 87 | log_group_data = server._force_update_check(force, DATA) 88 | 89 | if log_group == None: 90 | try: 91 | r = server._config_update(server.url + _create_url(log_group_data['common.ALLTYPES_NAME']), log_group_data) 92 | if r.code == 200: return True 93 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 94 | except KeyError as err: 95 | err_msg = 'Error: No log group identified in DATA | Key Error: {}'.format(err) 96 | raise KepError(err_msg) 97 | else: 98 | r = server._config_update(server.url + _create_url(log_group), log_group_data) 99 | if r.code == 200: return True 100 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 101 | 102 | def get_log_group(server: server, log_group: str) -> dict: 103 | '''Returns the properties of the `"log group"` object. 104 | 105 | :param server: instance of the `server` class 106 | :param log_group: name of log group to retrieve 107 | 108 | :return: Dict of properties for the log group requested 109 | 110 | :raises KepHTTPError: If urllib provides an HTTPError 111 | :raises KepURLError: If urllib provides an URLError 112 | ''' 113 | r = server._config_get(server.url + _create_url(log_group)) 114 | return r.payload 115 | 116 | def get_all_log_groups(server: server, *, options: dict = None) -> list: 117 | '''Returns the properties of all log group objects for Kepware's Datalogger. Returned object is JSON list. 118 | 119 | :param server: instance of the `server` class 120 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of log groups. Options are 'filter', 121 | 'sortOrder', 'sortProperty', 'pageNumber', and 'pageSize'. Only used when exchange_name is not defined. 122 | 123 | :return: list of properties for all log groups requested 124 | 125 | :raises KepHTTPError: If urllib provides an HTTPError 126 | :raises KepURLError: If urllib provides an URLError 127 | ''' 128 | r = server._config_get(f'{server.url}{_create_url()}', params= options) 129 | return r.payload 130 | 131 | def enable_log_group(server: server, log_group: str) -> bool: 132 | '''Enable the `"log group"`. 133 | 134 | :param server: instance of the `server` class 135 | :param log_group: name of log group to enable 136 | 137 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 138 | 139 | :raises KepHTTPError: If urllib provides an HTTPError 140 | :raises KepURLError: If urllib provides an URLError 141 | ''' 142 | DATA = {ENABLE_PROPERTY: True} 143 | return modify_log_group(server, DATA, log_group= log_group) 144 | 145 | def disable_log_group(server: server, log_group: str) -> bool: 146 | '''Disable the log group. Returned object is JSON. 147 | 148 | :param server: instance of the `server` class 149 | :param log_group: name of log group to enable 150 | 151 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 152 | 153 | :raises KepHTTPError: If urllib provides an HTTPError 154 | :raises KepURLError: If urllib provides an URLError 155 | ''' 156 | DATA = {ENABLE_PROPERTY: False} 157 | return modify_log_group(server, DATA, log_group= log_group) 158 | 159 | def reset_column_mapping_service(server: server, log_group: str, job_ttl: int = None) -> KepServiceResponse: 160 | '''Executes a ResetColumnMapping serivce call to the log group 161 | 162 | :param server: instance of the `server` class 163 | :param log_group: name of log group to enable 164 | :param job_ttl: *(optional)* Determines the number of seconds a job instance will exist following completion. 165 | 166 | :return: `KepServiceResponse` instance with job information 167 | 168 | :raises KepHTTPError: If urllib provides an HTTPError (If not HTTP code 202 [Accepted] or 429 [Too Busy] returned) 169 | :raises KepURLError: If urllib provides an URLError 170 | ''' 171 | 172 | url = server.url + _create_url(log_group) + SERVICES_ROOT + '/ResetColumnMapping' 173 | job = server._kep_service_execute(url, None, job_ttl) 174 | return job -------------------------------------------------------------------------------- /examples/iot gateway example.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | # IoT Gateway Example - Simple example on how to manage a connection and 8 | # execute various calls for the IoT Gateway components of the Kepware 9 | # Configuration API 10 | 11 | from kepconfig import connection, error 12 | from kepconfig.connectivity import channel 13 | import kepconfig.iot_gateway as iot 14 | 15 | # Agent name and Type to be used - constants from kepconfig.iotgateway 16 | # can be used to identify the type of agent 17 | agent_name = 'MQTT Agent 1' 18 | agent_type = iot.MQTT_CLIENT_AGENT 19 | 20 | #Tag Address to add to the IoT agent 21 | iot_item_name = "Channel1.Device1.Tag1" 22 | 23 | def ErrorHandler(err): 24 | # Generic Handler for exception errors 25 | if isinstance(err, error.KepHTTPError): 26 | print(err.code) 27 | print(err.msg) 28 | print(err.url) 29 | print(err.hdrs) 30 | print(err.payload) 31 | elif isinstance(err, error.KepURLError): 32 | print(err.url) 33 | print(err.reason) 34 | elif isinstance(err, error.KepError): 35 | print(err.msg) 36 | else: 37 | print('Different Exception Received: {}'.format(err)) 38 | 39 | # This creates a server reference that is used to target all modifications of 40 | # the Kepware configuration 41 | server = connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '') 42 | 43 | 44 | # Add a Channel using the "Simulator Driver" with device and tags. 45 | # These tags will be added to the IoT Agent. 46 | channel_data = { 47 | "common.ALLTYPES_NAME": "Channel1", 48 | "common.ALLTYPES_DESCRIPTION": "This is the test channel created", 49 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Simulator", 50 | "devices": [ 51 | { 52 | "common.ALLTYPES_NAME": "Device1", 53 | "common.ALLTYPES_DESCRIPTION": "Hello, new description", 54 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "Simulator", 55 | "servermain.DEVICE_MODEL": 0, 56 | "tags": [ 57 | { 58 | "common.ALLTYPES_NAME": "Tag1", 59 | "common.ALLTYPES_DESCRIPTION": "Ramping Read/Write tag used to verify client connection", 60 | "servermain.TAG_ADDRESS": "R0001", 61 | "servermain.TAG_DATA_TYPE": 5, 62 | "servermain.TAG_READ_WRITE_ACCESS": 1, 63 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 64 | "servermain.TAG_SCALING_TYPE": 0 65 | }, 66 | { 67 | "common.ALLTYPES_NAME": "Tag2", 68 | "common.ALLTYPES_DESCRIPTION": "Constant Read/Write tag used to verify client connection", 69 | "servermain.TAG_ADDRESS": "K0001", 70 | "servermain.TAG_DATA_TYPE": 5, 71 | "servermain.TAG_READ_WRITE_ACCESS": 1, 72 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 73 | "servermain.TAG_SCALING_TYPE": 0 74 | } 75 | ] 76 | } 77 | ] 78 | } 79 | try: 80 | print("{} - {}".format("Adding Channel, Device and tags", channel.add_channel(server,channel_data))) 81 | except Exception as err: 82 | ErrorHandler(err) 83 | 84 | 85 | # Add the MQTT Agent with the appropriate parameters 86 | agent_data = { 87 | "common.ALLTYPES_NAME": agent_name, 88 | "iot_gateway.AGENTTYPES_ENABLED": True, 89 | "iot_gateway.MQTT_CLIENT_URL": "tcp://localhost:1883", 90 | "iot_gateway.MQTT_CLIENT_TOPIC": "iotgateway", 91 | "iot_gateway.MQTT_CLIENT_QOS": 1, 92 | "iot_gateway.AGENTTYPES_RATE_MS": 10000, 93 | "iot_gateway.AGENTTYPES_PUBLISH_FORMAT": 0, 94 | "iot_gateway.AGENTTYPES_MAX_EVENTS": 1000, 95 | "iot_gateway.AGENTTYPES_TIMEOUT_S": 5, 96 | "iot_gateway.AGENTTYPES_MESSAGE_FORMAT": 0, 97 | "iot_gateway.MQTT_CLIENT_CLIENT_ID": "", 98 | "iot_gateway.MQTT_CLIENT_USERNAME": "", 99 | "iot_gateway.MQTT_CLIENT_PASSWORD": "" 100 | } 101 | try: 102 | print("{} - {}".format("Add the MQTT Agent", iot.agent.add_iot_agent(server, agent_data, agent_type))) 103 | except Exception as err: 104 | ErrorHandler(err) 105 | 106 | # Modify a property of the Agent 107 | agent_data = { 108 | } 109 | agent_data['common.ALLTYPES_DESCRIPTION'] = 'This is the test agent created' 110 | try: 111 | print("{} - {}".format("Modify property in the MQTT Agent", iot.agent.modify_iot_agent(server,agent_data, agent= agent_name, agent_type= agent_type))) 112 | except Exception as err: 113 | ErrorHandler(err) 114 | 115 | # Get Agent the properties for the agent that was created. It will return the 116 | # JSON of the properties 117 | try: 118 | print("{} - {}".format("Read properties of the MQTT Agent", iot.agent.get_iot_agent(server, agent_name, agent_type))) 119 | except Exception as err: 120 | ErrorHandler(err) 121 | 122 | # Get a list of all MQTT Agents that are configured 123 | try: 124 | print("{} - {}".format("Getting list of MQTT Agents", iot.agent.get_all_iot_agents(server, agent_type))) 125 | except Exception as err: 126 | ErrorHandler(err) 127 | 128 | # Add an tag or IoT Item to the MQTT Agent to start publishing 129 | iot_item_data = { 130 | "common.ALLTYPES_NAME": iot_item_name, 131 | "common.ALLTYPES_DESCRIPTION": "", 132 | "iot_gateway.IOT_ITEM_SERVER_TAG": iot_item_name, 133 | "iot_gateway.IOT_ITEM_USE_SCAN_RATE": True, 134 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 1000, 135 | "iot_gateway.IOT_ITEM_SEND_EVERY_SCAN": False, 136 | "iot_gateway.IOT_ITEM_DEADBAND_PERCENT": 0, 137 | "iot_gateway.IOT_ITEM_ENABLED": True, 138 | "iot_gateway.IOT_ITEM_DATA_TYPE": 5 139 | } 140 | try: 141 | print("{} - {}".format("Add new tag to the MQTT Agent", iot.iot_items.add_iot_item(server, iot_item_data, agent_name, agent_type))) 142 | except Exception as err: 143 | ErrorHandler(err) 144 | 145 | # Modify properties of the tag or IoT Item. If the "common.ALLTYPES_Name" is defined 146 | # the "modify_iot_item" function does not need have the agent name as an input 147 | modify_iot_item = { 148 | "common.ALLTYPES_NAME": iot_item_name, 149 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 500 150 | } 151 | try: 152 | print("{} - {}".format("Modify the tag or IoT Item added", iot.iot_items.modify_iot_item(server, modify_iot_item, agent_name, agent_type))) 153 | except Exception as err: 154 | ErrorHandler(err) 155 | 156 | # Modify properties of the tag or IoT Item. (Version 2) It is not necessary to pass JSON 157 | # with the "common.ALLTYPES_Name" of the tag to modify. It can be passed as a input 158 | # for the "modify_iot_item" function. "Force" will force the 159 | # update to the Kepware Server, if "FORCE_UPDATE" not provided in the JSON data. 160 | modify_iot_item = { 161 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 2000 162 | } 163 | try: 164 | print("{} - {}".format("Modify the tag or IoT Item added again", iot.iot_items.modify_iot_item(server, modify_iot_item, agent_name, agent_type, iot_item= iot_item_name, force = True))) 165 | except Exception as err: 166 | ErrorHandler(err) 167 | 168 | # Read the tag or IoT Item configured in the MQTT Agent 169 | try: 170 | print("{} - {}".format("Read the properties of the IoT Item", iot.iot_items.get_iot_item(server, iot_item_name, agent_name, agent_type))) 171 | except Exception as err: 172 | ErrorHandler(err) 173 | 174 | # Get a list of all tags or IoT Items configured in the MQTT Agent 175 | try: 176 | print("{} - {}".format("Get a list of all the IoT Items configured in the MQTT Agent", iot.iot_items.get_all_iot_items(server, agent_name, agent_type))) 177 | except Exception as err: 178 | ErrorHandler(err) 179 | 180 | # Delete a tag or IoT Item configured in the MQTT Agent 181 | try: 182 | print("{} - {}".format("Delete the IoT Item", iot.iot_items.del_iot_item(server, iot_item_name, agent_name, agent_type))) 183 | except Exception as err: 184 | ErrorHandler(err) 185 | 186 | # Delete the MQTT Agent 187 | try: 188 | print("{} - {}".format("Delete the MQTT Agent", iot.agent.del_iot_agent(server, agent_name, agent_type))) 189 | except Exception as err: 190 | ErrorHandler(err) -------------------------------------------------------------------------------- /kepconfig/connectivity/channel.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for 4 | # license information. 5 | # -------------------------------------------------------------------------- 6 | 7 | 8 | r"""`channel` exposes an API to allow modifications (add, delete, modify) to 9 | channel objects within the Kepware Configuration API 10 | """ 11 | 12 | 13 | import inspect 14 | from ..connection import server 15 | from ..error import KepHTTPError, KepError 16 | from ..utils import _url_parse_object 17 | from typing import Union 18 | from . import device 19 | 20 | CHANNEL_ROOT = '/project/channels' 21 | 22 | def _create_url(channel = None): 23 | '''Creates url object for the "channel" branch of Kepware's project tree. Used 24 | to build a part of Kepware Configuration API URL structure 25 | 26 | Returns the channel specific url when a value is passed as the channel name. 27 | ''' 28 | 29 | if channel == None: 30 | return CHANNEL_ROOT 31 | else: 32 | return '{}/{}'.format(CHANNEL_ROOT,_url_parse_object(channel)) 33 | 34 | def add_channel(server: server, DATA: Union[dict, list]) -> Union[bool, list]: 35 | '''Add a `"channel"` or multiple `"channel"` objects to Kepware. Can be used to pass children of a channel object 36 | such as devices and tags/tag groups. This allows you to create a channel, it's devices and tags 37 | all in one function, if desired. 38 | 39 | Additionally it can be used to pass a list of channels and it's children to be added all at once. 40 | 41 | :param server: instance of the `server` class 42 | :param DATA: Dict of the channel and it's children 43 | expected by Kepware Configuration API 44 | 45 | :return: True - If a "HTTP 201 - Created" is received from Kepware server 46 | :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 47 | channels added that failed. 48 | 49 | :raises KepHTTPError: If urllib provides an HTTPError 50 | :raises KepURLError: If urllib provides an URLError 51 | ''' 52 | 53 | r = server._config_add(server.url + _create_url(), DATA) 54 | if r.code == 201: return True 55 | elif r.code == 207: 56 | errors = [] 57 | for item in r.payload: 58 | if item['code'] != 201: 59 | errors.append(item) 60 | return errors 61 | else: 62 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 63 | 64 | def del_channel(server: server, channel: str) -> bool: 65 | '''Delete a `"channel"` object in Kepware. This will delete all children as well 66 | 67 | :param server: instance of the `server` class 68 | :param channel: name of channel 69 | 70 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 71 | 72 | :raises KepHTTPError: If urllib provides an HTTPError 73 | :raises KepURLError: If urllib provides an URLError 74 | ''' 75 | 76 | r = server._config_del(server.url + _create_url(channel)) 77 | if r.code == 200: return True 78 | else: 79 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 80 | 81 | def modify_channel(server: server, DATA: dict, *, channel: str = None, force: bool = False) -> bool: 82 | '''Modify a channel object and it's properties in Kepware. If a `"channel"` is not provided as an input, 83 | you need to identify the channel in the *'common.ALLTYPES_NAME'* property field in `"DATA"`. It will 84 | assume that is the channel that is to be modified. 85 | 86 | :param server: instance of the `server` class 87 | :param DATA: Dict of the `channel` properties to be modified 88 | :param channel: *(optional)* name of channel to modify. Only needed if not existing in `"DATA"` 89 | :param force: *(optional)* if True, will force the configuration update to the Kepware server 90 | 91 | :return: True - If a "HTTP 200 - OK" is received from Kepware server 92 | 93 | :raises KepHTTPError: If urllib provides an HTTPError 94 | :raises KepURLError: If urllib provides an URLError 95 | ''' 96 | 97 | channel_data = server._force_update_check(force, DATA) 98 | if channel == None: 99 | try: 100 | r = server._config_update(server.url + _create_url(channel_data['common.ALLTYPES_NAME']), channel_data) 101 | if r.code == 200: return True 102 | else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 103 | except KeyError as err: 104 | err_msg = 'Error: No Channel identified in DATA | Key Error: {}'.format(err) 105 | raise KepError(err_msg) 106 | # except Exception as e: 107 | # return 'Error: Error with {}: {}'.format(inspect.currentframe().f_code.co_name, str(e)) 108 | else: 109 | r = server._config_update(server.url + _create_url(channel), channel_data) 110 | if r.code == 200: return True 111 | else: 112 | raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 113 | 114 | def get_channel(server: server, channel: str) -> dict: 115 | '''Returns the properties of the channel object. 116 | 117 | :param server: instance of the `server` class 118 | :param channel: name of channel 119 | 120 | :return: Dict of data for the channel requested 121 | 122 | :raises KepHTTPError: If urllib provides an HTTPError 123 | :raises KepURLError: If urllib provides an URLError 124 | ''' 125 | 126 | r = server._config_get(server.url + _create_url(channel)) 127 | return r.payload 128 | 129 | def get_all_channels(server: server, *, options: dict = None) -> list: 130 | '''Returns list of all channel objects and their properties. 131 | 132 | :param server: instance of the `server` class 133 | :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of channels. Options are `filter`, 134 | `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 135 | 136 | :return: List of data for all channels in Kepware server 137 | 138 | :raises KepHTTPError: If urllib provides an HTTPError 139 | :raises KepURLError: If urllib provides an URLError 140 | ''' 141 | 142 | r = server._config_get(server.url + _create_url(), params= options) 143 | return r.payload 144 | 145 | def get_channel_structure(server: server, channel: str) -> dict: 146 | '''Returns the properties of `"channel"` and includes all `"devices"` and the `"tag"` and `"tag group"` objects for a 147 | channel in Kepware. Returned object is a dict of channel properties including a device list with 148 | tag lists and tag group lists. 149 | 150 | The returned object resembles the example below, nested based on how many 151 | levels the tag_group namespace has tags or tag_groups: 152 | 153 | Example return: 154 | 155 | { 156 | channel_properties, 157 | 'devices: [ 158 | { 159 | device1_properties, 160 | 'tags': [tag1_dict, tag2_dict,...], 161 | 'tag_groups':[ 162 | { 163 | tag_group1_properties, 164 | 'tags': [tag1_dict, tag2_dict,...] 165 | 'tag_groups':[sub_group1, subgroup2,...] 166 | }, 167 | { 168 | tag_group2_properties, 169 | 'tags': [tag1_dict, tag2_dict,...] 170 | 'tag_groups':[sub_group1, subgroup2,...] 171 | },...] 172 | },...] 173 | } 174 | 175 | :param server: instance of the `server` class 176 | :param channel: name of channel 177 | 178 | :return: Dict of data for the channel structure requested for `"channel"` 179 | 180 | :raises KepHTTPError: If urllib provides an HTTPError 181 | :raises KepURLError: If urllib provides an URLError 182 | ''' 183 | 184 | channel_properties = get_channel(server, channel) 185 | device_list = device.get_all_devices(server,channel) 186 | for dev in device_list: 187 | device_properties = [] 188 | dev_struct = device.get_device_structure(server,channel + '.' + dev['common.ALLTYPES_NAME']) 189 | device_properties.append(dev_struct) 190 | return {**channel_properties,'device': device_properties} --------------------------------------------------------------------------------