├── .gitattributes ├── .gitignore ├── Config API ├── Config API Endpoint and Property Viewer │ ├── API_gui.py │ ├── README.md │ ├── server.py │ └── widgets.py ├── Convert Tags to Kepware CSV │ ├── README.md │ └── tag_export.py ├── Count Channels and Devices │ ├── README.md │ └── count_channels_and_devices.py ├── Enable Secondary Server │ ├── README.md │ ├── setup.json │ └── standby.py ├── Get Event Log │ ├── GetEventLog.py │ └── README.md ├── Project Synchronization │ ├── ProjectSync.py │ ├── README.md │ └── setup.json └── README.md ├── Datalogger ├── CSV to DataLogger API │ ├── CSVtoDataLogger.py │ ├── README.md │ ├── csvs │ │ └── logitems_example.csv │ ├── objs │ │ └── logitem.json │ └── setup.json ├── DataLogger to CSV │ ├── DataLoggerToCSV.py │ ├── README.md │ ├── csvs │ │ └── logitems_export.csv │ └── setup.json └── Powershell │ ├── Add Tags from CSV to Log Group │ ├── addLogItemstoLogGroup.ps1 │ ├── csvs │ │ ├── auth.csv │ │ ├── deviceItems.csv │ │ └── logGroup.csv │ └── readme.md │ ├── Export Log Items to CSV │ ├── csvs │ │ ├── auth.csv │ │ └── logGroup.csv │ ├── exportLogItemsToCsv.ps1 │ └── readme.md │ └── Use CSV to Configure Column Mappings │ ├── configureColumnMappings.ps1 │ ├── csvs │ ├── auth.csv │ ├── columnMappings.csv │ └── logGroup.csv │ └── readme.md ├── IoT Gateway ├── Azure IoT Hub Integration │ ├── autodeploy_kepware_and_azure │ │ ├── Readme.md │ │ ├── createNewAsset.py │ │ ├── kepware_object_snippets │ │ │ ├── agents.json │ │ │ ├── arrayOfMqttAgents.json │ │ │ └── templates │ │ │ │ ├── BACnet Channel and Device - Kepware JSON Snippet.json │ │ │ │ ├── BACnet Device and Tags - Kepware JSON Snippet.json │ │ │ │ ├── Single IoT Item - Kepware JSON Snippet.json │ │ │ │ ├── Single MQTT Agent for IoT Hub - Kepware JSON Snippet.json │ │ │ │ └── Single Tag - Kepware JSON Snippet.json │ │ └── setup.json │ └── autodeploy_kepware_for_BACnetIP │ │ ├── Readme.md │ │ ├── auto_deploy.py │ │ ├── csvs │ │ └── source.csv │ │ ├── objs │ │ ├── agent.json │ │ ├── agent_item.json │ │ ├── channel.json │ │ ├── device.json │ │ └── tag.json │ │ └── setup.json ├── Google Cloud Integration │ ├── README.md │ └── Update JWT and MQTT Agent.py ├── README.md └── Read and Write Examples │ └── Node.js │ └── Read_Calc_Write Examples │ ├── Generic_Read_Write │ ├── Kepware_IoTFunction_demo.js │ ├── README.md │ └── pics │ │ ├── Instructions - Generic IoT Demo.docx │ │ ├── diagram.png │ │ ├── output.png │ │ └── qc.png │ ├── Kepware_iotgateway_functions.js │ ├── Loading_Data_third_party_API_to_Kepware │ ├── Kepware_IotGW_App.js │ ├── README.md │ └── pics │ │ ├── Instructions - GET Third Party.docx │ │ ├── diagram.png │ │ └── qc.png │ ├── NodeJsSample.opf │ └── README.md ├── LICENSE ├── README.md └── docs └── Repo-Guidelines.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | #*.gitignore 3 | *.pem 4 | _notes 5 | 6 | _new* 7 | _test* 8 | _build* 9 | package-lock.json 10 | 11 | ###################################################################### 12 | # Node.js Specific gitgnore 13 | # Logs 14 | logs 15 | *.log 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | lerna-debug.log* 20 | 21 | # Diagnostic reports (https://nodejs.org/api/report.html) 22 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 23 | 24 | # Runtime data 25 | pids 26 | *.pid 27 | *.seed 28 | *.pid.lock 29 | 30 | # Directory for instrumented libs generated by jscoverage/JSCover 31 | lib-cov 32 | 33 | # Coverage directory used by tools like istanbul 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | .nyc_output 39 | 40 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 41 | .grunt 42 | 43 | # Bower dependency directory (https://bower.io/) 44 | bower_components 45 | 46 | # node-waf configuration 47 | .lock-wscript 48 | 49 | # Compiled binary addons (https://nodejs.org/api/addons.html) 50 | build/Release 51 | 52 | # Dependency directories 53 | node_modules/ 54 | jspm_packages/ 55 | 56 | # Snowpack dependency directory (https://snowpack.dev/) 57 | web_modules/ 58 | 59 | # TypeScript cache 60 | *.tsbuildinfo 61 | 62 | # Optional npm cache directory 63 | .npm 64 | 65 | # Optional eslint cache 66 | .eslintcache 67 | 68 | # Microbundle cache 69 | .rpt2_cache/ 70 | .rts2_cache_cjs/ 71 | .rts2_cache_es/ 72 | .rts2_cache_umd/ 73 | 74 | # Optional REPL history 75 | .node_repl_history 76 | 77 | # Output of 'npm pack' 78 | *.tgz 79 | 80 | # Yarn Integrity file 81 | .yarn-integrity 82 | 83 | # dotenv environment variables file 84 | .env 85 | .env.test 86 | 87 | # parcel-bundler cache (https://parceljs.org/) 88 | .cache 89 | .parcel-cache 90 | 91 | # Next.js build output 92 | .next 93 | out 94 | 95 | # Nuxt.js build / generate output 96 | .nuxt 97 | dist 98 | 99 | # Gatsby files 100 | .cache/ 101 | # Comment in the public line in if your project uses Gatsby and not Next.js 102 | # https://nextjs.org/blog/next-9-1#public-directory-support 103 | # public 104 | 105 | # vuepress build output 106 | .vuepress/dist 107 | 108 | # Serverless directories 109 | .serverless/ 110 | 111 | # FuseBox cache 112 | .fusebox/ 113 | 114 | # DynamoDB Local files 115 | .dynamodb/ 116 | 117 | # TernJS port file 118 | .tern-port 119 | 120 | # Stores VSCode versions used for testing VSCode extensions 121 | .vscode-test 122 | 123 | # yarn v2 124 | .yarn/cache 125 | .yarn/unplugged 126 | .yarn/build-state.yml 127 | .yarn/install-state.gz 128 | .pnp.* 129 | 130 | ######################################################################### 131 | # Python Specific gitgnore 132 | # Byte-compiled / optimized / DLL files 133 | __pycache__/ 134 | *.py[cod] 135 | *$py.class 136 | 137 | # C extensions 138 | *.so 139 | 140 | # Distribution / packaging 141 | .Python 142 | build/ 143 | develop-eggs/ 144 | # dist/ 145 | downloads/ 146 | eggs/ 147 | .eggs/ 148 | lib/ 149 | lib64/ 150 | parts/ 151 | sdist/ 152 | var/ 153 | wheels/ 154 | pip-wheel-metadata/ 155 | share/python-wheels/ 156 | *.egg-info/ 157 | .installed.cfg 158 | *.egg 159 | MANIFEST 160 | 161 | # PyInstaller 162 | # Usually these files are written by a python script from a template 163 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 164 | *.manifest 165 | *.spec 166 | 167 | # Installer logs 168 | pip-log.txt 169 | pip-delete-this-directory.txt 170 | 171 | # Unit test / coverage reports 172 | htmlcov/ 173 | .tox/ 174 | .nox/ 175 | .coverage 176 | .coverage.* 177 | .cache 178 | nosetests.xml 179 | coverage.xml 180 | *.cover 181 | *.py,cover 182 | .hypothesis/ 183 | .pytest_cache/ 184 | 185 | # Translations 186 | *.mo 187 | *.pot 188 | 189 | # Django stuff: 190 | *.log 191 | local_settings.py 192 | db.sqlite3 193 | db.sqlite3-journal 194 | 195 | # Flask stuff: 196 | instance/ 197 | .webassets-cache 198 | 199 | # Scrapy stuff: 200 | .scrapy 201 | 202 | # Sphinx documentation 203 | docs/_build/ 204 | 205 | # PyBuilder 206 | target/ 207 | 208 | # Jupyter Notebook 209 | .ipynb_checkpoints 210 | 211 | # IPython 212 | profile_default/ 213 | ipython_config.py 214 | 215 | # pyenv 216 | .python-version 217 | 218 | # pipenv 219 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 220 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 221 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 222 | # install all needed dependencies. 223 | #Pipfile.lock 224 | 225 | # celery beat schedule file 226 | celerybeat-schedule 227 | 228 | # SageMath parsed files 229 | *.sage.py 230 | 231 | # Environments 232 | .env 233 | .venv 234 | env/ 235 | venv/ 236 | ENV/ 237 | env.bak/ 238 | venv.bak/ 239 | 240 | # Spyder project settings 241 | .spyderproject 242 | .spyproject 243 | 244 | # Rope project settings 245 | .ropeproject 246 | 247 | # mkdocs documentation 248 | /site 249 | 250 | # mypy 251 | .mypy_cache/ 252 | .dmypy.json 253 | dmypy.json 254 | 255 | # Pyre type checker 256 | .pyre/ 257 | -------------------------------------------------------------------------------- /Config API/Config API Endpoint and Property Viewer/API_gui.py: -------------------------------------------------------------------------------- 1 | # ***************************************************************************** 2 | # * 3 | # * This file is copyright (c) PTC, Inc. 4 | # * All rights reserved. 5 | # * 6 | # * Name: API_gui.py 7 | # * Description: A simple GUI app that provides a visualization of the Kepware 8 | # * Configuration API. 9 | # * 10 | # * 11 | # * Update History: 12 | # * 0.0.1: Initial Release 13 | # * 0.0.2: Allowed UA Gateway to be processed 14 | # * Prevent Plug-in root branches from being created if not installed 15 | # * on server 16 | # * 0.0.3: Added Search Feature - Search in text box for api data 17 | # * Provided a Methods output in text to associate type definition 18 | # * access properties to HTTP Methods 19 | # * 0.0.4: Added Doc Endpoint Information to text box to provide user additional 20 | # * information 21 | # * 22 | # * Version: 0.0.4 23 | # ****************************************************************************** 24 | 25 | from tkinter import * 26 | from tkinter import ttk, messagebox 27 | 28 | import json 29 | import re 30 | from widgets import searchbar, treevscroll, textscroll 31 | from server import server_doc 32 | 33 | COLUMNS = ['doc_path', 'endpoint', 'parent_endpoint'] 34 | 35 | class App(ttk.Frame): 36 | def __init__(self, *args, **kwargs): 37 | super().__init__(*args, **kwargs) 38 | 39 | 40 | # Kepware Connection config 41 | self.server = server_doc(None, None, None, None) 42 | self.api_root = None 43 | 44 | self.grid(column=0, row=0, sticky=NSEW) 45 | self.columnconfigure(0, weight=1, uniform='test') 46 | self.rowconfigure(0, weight=1) 47 | self.rowconfigure(1, weight=0) 48 | self.rowconfigure(2, weight=10) 49 | 50 | # Access Frame with credentials and host information 51 | self.accessframe = ttk.Frame(self) 52 | self.accessframe.grid(column=0, row=0, sticky=NSEW) 53 | 54 | self.serverframe = ttk.Labelframe(self.accessframe, text='Kepware Server Address', padding="3 3 12 12") 55 | self.serverframe.grid(column=0, row=0, sticky=NSEW) 56 | 57 | self.hostVar = StringVar() 58 | self.hostVar.set('localhost') 59 | self.hostEntry = ttk.Entry(self.serverframe, textvariable=self.hostVar) 60 | self.hostEntry.grid(column=1, row=0, sticky=NSEW) 61 | Label(self.serverframe, text='Host/IP:').grid(column=0, row=0, sticky=E) 62 | 63 | self.portVar = StringVar() 64 | self.portVar.set('57512') 65 | self.portEntry = ttk.Entry(self.serverframe, textvariable=self.portVar) 66 | self.portEntry.grid(column=1, row=1, sticky=NSEW) 67 | Label(self.serverframe, text='Port:').grid(column=0, row=1, sticky=E) 68 | 69 | self.__optionList = ['https', 'http'] 70 | self.httpVar = StringVar() 71 | self.httpSelect = ttk.OptionMenu(self.serverframe, self.httpVar, self.__optionList[0], *self.__optionList, command= self.httpSelectEvent) 72 | self.httpSelect.grid(column=1, row=2, sticky=NW) 73 | self.httpSelect.configure() 74 | 75 | self.trustCertsVar = BooleanVar() 76 | self.trustCertsSelect = ttk.Checkbutton(self.serverframe, variable=self.trustCertsVar, text='Trust All Certs?') 77 | self.trustCertsSelect.grid(column=1, row=3, sticky=NW) 78 | 79 | 80 | self.credframe = ttk.Labelframe(self.accessframe, text='Authorization', padding="3 3 12 12") 81 | self.credframe.grid(column=1, row=0, sticky=NSEW) 82 | 83 | self.userVar = StringVar() 84 | self.userVar.set('Administrator') 85 | self.userEntry = ttk.Entry(self.credframe, textvariable=self.userVar) 86 | self.userEntry.grid(column=1, row=0, sticky=NSEW) 87 | Label(self.credframe, text='Username:').grid(column=0, row=0, sticky=E) 88 | 89 | self.passVar = StringVar() 90 | self.passVar.set('') 91 | self.passEntry = ttk.Entry(self.credframe, textvariable=self.passVar) 92 | self.passEntry.grid(column=1, row=1, sticky=NSEW) 93 | Label(self.credframe, text='Password:').grid(column=0, row=1, sticky=E) 94 | 95 | self.btnFrame = ttk.Frame(self.accessframe) 96 | self.btnFrame.grid(column=3, row=0, sticky=NSEW) 97 | self.connectBtn = ttk.Button(self.btnFrame, text='Connect', command=self.connect) 98 | self.connectBtn.grid(column=0, row=0, sticky=W) 99 | self.disconnectBtn = ttk.Button(self.btnFrame, text='Disconnect', command=self.disconnect, state=DISABLED) 100 | self.disconnectBtn.grid(column=0, row=1, sticky=W) 101 | 102 | # Search Bar 103 | self.searchPos = {'idx': '1.0'} 104 | self.searchBar = searchbar(self, column=0, row=1, command= self.search) 105 | 106 | # Frame to Visualize tree and doc text 107 | self.dataframe = PanedWindow(self, orient=HORIZONTAL, sashpad='2p', sashwidth='4p', sashrelief='sunken', showhandle=TRUE) 108 | self.dataframe.grid(column=0, row=2, sticky=NSEW) 109 | self.dataframe.rowconfigure(0, weight=1) 110 | 111 | # Tree Browser Object 112 | self.treeframe = treevscroll(self.dataframe, row=0, columns=COLUMNS) 113 | self.dataframe.add(self.treeframe) 114 | self.event_add('<>', '', '') 115 | self.treeframe.treev.tag_bind('item', '<>', self.item_click) 116 | self.treeframe.treev.tag_bind('root', '<>', self.root_click) 117 | 118 | # Text Frame Object for Output of Documentation 119 | self.textframe = textscroll(self.dataframe, row=0) 120 | self.dataframe.add(self.textframe) 121 | self.textframe.text.config(state=DISABLED) 122 | self.textframe.text.tag_config('endpoint', foreground='blue') 123 | self.textframe.text.tag_config('methods') 124 | self.textframe.text.tag_config('url', underline=1) 125 | 126 | # Event for selecting Connect button 127 | def connect(self): 128 | http = False 129 | if self.httpVar.get() == 'https': 130 | http = True 131 | self.server = server_doc(self.hostVar.get(), self.portVar.get(), self.userVar.get(), self.passVar.get(), http) 132 | self.server.SSL_trust_all_certs = self.trustCertsVar.get() 133 | try: 134 | self.api_root = self.server.get_doc('/config/v1/doc') 135 | except Exception as err: 136 | self.server.ErrorHandler(err) 137 | else: 138 | self.build_tree_root(self.api_root) 139 | self.connectBtn.state(('disabled',)) 140 | self.disconnectBtn.state(('!disabled',)) 141 | self.hostEntry.configure(state=DISABLED) 142 | self.portEntry.configure(state=DISABLED) 143 | self.userEntry.configure(state=DISABLED) 144 | self.passEntry.configure(state=DISABLED) 145 | self.httpSelect.configure(state=DISABLED) 146 | 147 | # Event for selecting Disconnect button 148 | def disconnect(self): 149 | root_iids = self.treeframe.treev.get_children() 150 | [self.treeframe.treev.delete(branch) for branch in root_iids] 151 | self.update_text_window('') 152 | self.connectBtn.state(('!disabled',)) 153 | self.disconnectBtn.state(('disabled',)) 154 | self.httpSelect.configure(state=NORMAL) 155 | self.hostEntry.configure(state=NORMAL) 156 | self.portEntry.configure(state=NORMAL) 157 | self.userEntry.configure(state=NORMAL) 158 | self.passEntry.configure(state=NORMAL) 159 | 160 | # Event for searching 161 | #function to search string in text 162 | def search(self, *args): 163 | 164 | #remove tag 'found' from index 1 to END 165 | self.textframe.text.tag_remove('found', '1.0', END) 166 | 167 | #returns to widget currently in focus 168 | s = self.searchBar.input.get() 169 | if not s: 170 | return 171 | idx = self.searchPos['idx'] 172 | 173 | #searches for desired string from index 1 174 | idx = self.textframe.text.search(s, idx, nocase=1,) 175 | if not idx: 176 | self.searchPos['idx'] = '1.0' 177 | messagebox.showinfo('Not Found', f'"{s}" not found.') 178 | return 179 | 180 | #last index sum of current index and 181 | #length of text 182 | lastidx = '%s+%dc' % (idx, len(s)) 183 | 184 | #overwrite 'Found' at idx 185 | self.textframe.text.tag_add('found', idx, lastidx) 186 | self.searchPos['idx'] = lastidx 187 | self.textframe.text.see(idx) 188 | 189 | #mark located string as red 190 | self.textframe.text.tag_config('found', background='#3282F6', foreground='white') 191 | self.searchBar.input.focus_set() 192 | 193 | # Event for selecting protocol 194 | def httpSelectEvent(self, event): 195 | if event == 'http': 196 | self.trustCertsSelect.configure(state=DISABLED) 197 | else: 198 | self.trustCertsSelect.configure(state=NORMAL) 199 | 200 | # Event for Root branches 201 | def root_click(self, event: Event): 202 | item = event.widget.selection() 203 | info = event.widget.item(item[0]) 204 | r = self.server.get_doc(info['values'][COLUMNS.index('doc_path')]) 205 | self.update_text_window(r, info['values'][COLUMNS.index('endpoint')], info['values'][COLUMNS.index('doc_path')]) 206 | 207 | # Event for other branches 208 | def item_click(self, event: Event): 209 | item = event.widget.selection() 210 | info = event.widget.item(item[0]) 211 | r = self.server.get_doc(info['values'][COLUMNS.index('doc_path')]) 212 | self.update_text_window(r, info['values'][COLUMNS.index('endpoint')], info['values'][COLUMNS.index('doc_path')]) 213 | if info['text'] in ['tag_groups', 'tags']: 214 | return 215 | 216 | if 'child_collections' in r['type_definition']: 217 | [self.child_collection_insert(item[0], j) for j in r['type_definition']['child_collections']] 218 | 219 | # Init Driver tree branch 220 | def driver_tree_insert(self, root, driver): 221 | branch = self.treeframe.treev.insert(root, END, text=driver['display_name'], tags='driver') 222 | 223 | # Search doc keys 224 | children = [] 225 | efm_children = [] 226 | for j in driver.keys(): 227 | if j.startswith('doc') and j.find('doc_meters') == -1 and j.find('doc_meter_groups') == -1: 228 | name = j[4:] 229 | urls = [child for child in self.api_root if re.search(rf'/{name}$', child['endpoint'])] 230 | children.append([j,name, urls[0]]) 231 | # elif j.find('doc_meters') != -1 or j.find('doc_meter_groups') != -1: 232 | elif j.find('doc_meter_groups') != -1: 233 | name = j[4:] 234 | urls = [child for child in self.api_root if re.search(rf'/{name}$', child['endpoint'])] 235 | efm_children.append([j,name, urls[0]]) 236 | [self.treeframe.treev.insert(branch,END, text=name, tags='item', values=[driver[doc], url['endpoint']]) for doc, name, url in children] 237 | if efm_children: 238 | root_iids = self.treeframe.treev.get_children(branch) 239 | for iid in root_iids: 240 | childname = self.treeframe.treev.item(iid, option='text') 241 | if childname == 'devices': 242 | [self.treeframe.treev.insert(iid,END, text=name, tags='item', values=[driver[doc], url['endpoint']]) for doc, name, url in efm_children] 243 | pass 244 | 245 | 246 | # Init Plug-In tree branch 247 | def plug_in_collection_insert(self, root, collection: str): 248 | exist_children = [self.treeframe.treev.item(children)['text'] for children in self.treeframe.treev.get_children(root)] 249 | if collection.startswith('_') and collection not in exist_children: 250 | parent_endpoint = f'/config/v1/project/' 251 | # Find Children in API doc list 252 | filtered = [child for child in self.api_root if re.search(rf'/{collection}/(\w+)$', child['endpoint'])] 253 | # Filtered will return API endpoints for Plug-ins if it is installed. If the plug-in isn't installed, don't 254 | # create branch. 255 | if filtered: 256 | branch = self.treeframe.treev.insert(root, END, text=collection, values=['',f'{parent_endpoint}/{collection}',]) 257 | for item in filtered: 258 | self.treeframe.treev.insert(branch, END, text=re.search(rf'/{collection}/(\w+)$', item['endpoint']).groups()[0], tags='item', values=[item['doc'], item['endpoint'], parent_endpoint]) 259 | 260 | # Init All Endpoints tree 261 | def build_end_point_tree(self, root, endpoint_list, startpoint='/config/v1'): 262 | # Build Parents list at startpoint 263 | parents = [] 264 | for i in endpoint_list: 265 | matches = re.search(startpoint + r'\/([{]*[\w]*[}]*\?\S*|[{]*[\w]*[}]*)\/?\S*', i['endpoint']) 266 | if matches: 267 | parents.append(matches.groups()[0]) 268 | parents = list(set(parents)) 269 | parents.sort() 270 | for p in parents: 271 | p_url = f'{startpoint}/{p}' 272 | p_endpoint = [epdict for epdict in self.api_root if epdict['endpoint']== p_url] 273 | c_endpoints = [epdict for epdict in self.api_root if epdict['endpoint'].find(p_url) != -1] 274 | branch = None 275 | if 'doc' in p_endpoint[0]: 276 | branch = self.treeframe.treev.insert(root, END, text= p_url, tags='root', values=[p_endpoint[0]['doc'], p_url]) 277 | else: 278 | branch = self.treeframe.treev.insert(root, END, text= p_url, tags='names') 279 | self.build_end_point_tree(branch, c_endpoints, p_url) 280 | 281 | # Init Tree after connections 282 | def build_tree_root(self, info): 283 | root_list = [('Drivers', '/config/v1/doc/drivers', '/config/v1/doc/drivers', 'root'), ('Project','/config/v1/doc/project', '/config/v1/project', 'root'), ('Plug-ins','/config/v1/doc/project', '', 'root'), 284 | ('Admin','/config/v1/doc/admin', '/config/v1/admin', 'root'), ('All Endpoints','/config/v1/doc', '/config/v1/doc', 'root')] 285 | [self.treeframe.treev.insert('',END, text=i, open=False, tags=l, values=[j, k]) for i,j,k,l in root_list] 286 | 287 | root_iids = self.treeframe.treev.get_children() 288 | 289 | for i, (name, path, url, tag) in enumerate(root_list): 290 | r = self.server.get_doc(path) 291 | if name == 'Drivers': 292 | tree_items = sorted(r, key=lambda d: d['display_name']) 293 | [self.driver_tree_insert(root_iids[i], driver) for driver in tree_items] 294 | elif name in ['Project', 'Admin']: 295 | if r['type_definition']['child_collections']: 296 | tree_items = sorted(r['type_definition']['child_collections']) 297 | [self.child_collection_insert(root_iids[i], j) for j in tree_items] 298 | elif name == 'Plug-ins': 299 | if r['type_definition']['child_collections']: 300 | tree_items = sorted(r['type_definition']['child_collections']) 301 | [self.plug_in_collection_insert(root_iids[i], j) for j in tree_items] 302 | elif name == 'All Endpoints': 303 | tree_items = sorted(r, key=lambda d: d['endpoint']) 304 | self.build_end_point_tree(root_iids[i], tree_items) 305 | else: 306 | pass 307 | 308 | # Build Children tree branchs 309 | def child_collection_insert(self, root, collection): 310 | exist_children = [self.treeframe.treev.item(children)['text'] for children in self.treeframe.treev.get_children(root)] 311 | if collection not in ['devices', 'channels', 'meter_groups'] + exist_children and not collection.startswith('_'): 312 | # Find Collection in API doc list relative to parent path 313 | parent_endpoint= self.treeframe.treev.item(root)['values'][COLUMNS.index('endpoint')] 314 | if parent_endpoint in ['/config/v1/project', '/config/v1/admin']: 315 | col = [coldict for coldict in [cdict for cdict in self.api_root if 'doc' in cdict] if coldict['doc'].find(collection) != -1 and re.search(f'{parent_endpoint}/{collection}', coldict['endpoint'])] 316 | else: 317 | col = [coldict for coldict in [cdict for cdict in self.api_root if 'doc' in cdict] if coldict['doc'].find(collection) != -1 and re.search(parent_endpoint + r'\/\{\w+\}\/' + collection, coldict['endpoint'])] 318 | # Make sure a match is found 319 | if col: 320 | self.treeframe.treev.insert(root, END, text=collection, tags='item', values=[col[0]['doc'],col[0]['endpoint'], parent_endpoint]) 321 | 322 | def processMethods(self, DATA): 323 | methods = {'GET': True, 'DELETE': False, 'POST': False, 'PUT': False} 324 | if 'type_definition' in DATA: 325 | if DATA['type_definition']['can_create']: 326 | methods['POST'] = True 327 | if DATA['type_definition']['can_delete']: 328 | methods['DELETE'] = True 329 | if DATA['type_definition']['can_modify']: 330 | methods['PUT'] = True 331 | return methods 332 | 333 | # Update text window with data to present to user 334 | def update_text_window(self, DATA = None, endpoint = '', docEndpoint = ''): 335 | if endpoint != '': 336 | methods = self.processMethods(DATA) 337 | self.textframe.update([(f'ENDPOINT:\n', ('header','endpoint')), 338 | (f'{endpoint}\n\n',('endpoint', 'url')), 339 | (f'DOC ENDPOINT:\n', ('header', 'endpoint')), 340 | (f'{docEndpoint}\n\n',('endpoint', 'url')), 341 | (f'METHODS:\n{json.dumps(methods, indent=2)}\n\n', 'methods'), 342 | (f'DOCUMENTATION:\n{json.dumps(DATA, indent=2)}','doc')]) 343 | elif DATA: 344 | methods = self.processMethods(DATA) 345 | self.textframe.update([(f'METHODS:\n{json.dumps(methods, indent=2)}\n\n', 'methods'), (f'DOCUMENTATION:\n{json.dumps(DATA, indent=2)}','doc')]) 346 | else: 347 | self.textframe.clear() 348 | 349 | 350 | 351 | 352 | if __name__ == "__main__": 353 | root = Tk() 354 | root.title("Kepware Server API Documentation Viewer") 355 | root.minsize(width=500, height=200) 356 | root.columnconfigure(0, minsize=200, weight=1) 357 | root.rowconfigure(0, weight=1) 358 | sizegrip = ttk.Sizegrip(root) 359 | sizegrip.grid(row=1, sticky=SE) 360 | 361 | mainframe = App(root, padding="3 3 12 12") 362 | 363 | root.mainloop() -------------------------------------------------------------------------------- /Config API/Config API Endpoint and Property Viewer/README.md: -------------------------------------------------------------------------------- 1 | # Kepware Configuration API Endpoint and Property Viewer 2 | 3 | This provides a simple UI application to help navigate the documentation endpoints included in the Kepware Configuration API. The doc endpoints provide the API uri mapping and property definitions for all configuration items in Kepware. 4 | 5 | ## Install Python 6 | 7 | 1. Download [Python](https://www.python.org/downloads/) 8 | 2. During the install 9 | • Check box "Add python.exe to PATH" (can also be added later under "Advanced Options", check "Add Python to environment variables") 10 | • Select "Customized Installation" 11 | • Under "Optional Features", select "pip" 12 | 3. From a CMD, type ‘pip install kepconfig’ to install the [Kepconfig](https://github.com/PTCInc/Kepware-ConfigAPI-SDK-Python) package 13 | 14 | ## Enable the Configuration API on Kepware Server 15 | 16 | 1. Right-Click on the Administration Tool (green icon in the system tray) and select Settings 17 | 2. Set Enable and Enable HTTP to Yes 18 | 19 | ## Use the Application 20 | 21 | 1. Open a CMD and cd to the [API_gui.py](API_gui.py) location before launching the script or double-click on [API_gui.py](API_gui.py) from File Explorer. This will launch the UI. 22 | 2. Provide the correct parameters to connect to the Kepware Server instance and click the *Connect* button. 23 | 3. Once connected navigate the components in the tree by double clicking on each item. If there are children of the object, they will appear. 24 | 4. Search is available for the text within the property definition window. 25 | -------------------------------------------------------------------------------- /Config API/Config API Endpoint and Property Viewer/server.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | ''' 6 | Server Connection Class 7 | ''' 8 | from tkinter import messagebox 9 | from kepconfig import connection, error 10 | 11 | 12 | # Class for Kepware API Connection - extended for doc reads 13 | class server_doc(connection.server): 14 | def __init__(self, *args, **kwargs): 15 | super().__init__(*args, **kwargs) 16 | 17 | def get_doc(self,url): 18 | if self.SSL_on: 19 | proto = 'https' 20 | else: 21 | proto = 'http' 22 | path = '{}://{}:{}'.format(proto, self.host, self.port) 23 | r = self._config_get(path + url) 24 | return r.payload 25 | 26 | def ErrorHandler(self,err): 27 | # Generic Handler for exception errors 28 | if err.__class__ is error.KepError: 29 | print(err.msg) 30 | messagebox.showerror('Error', message=err.msg) 31 | elif err.__class__ is error.KepHTTPError: 32 | print(err.code) 33 | print(err.msg) 34 | print(err.url) 35 | print(err.hdrs) 36 | print(err.payload) 37 | messagebox.showerror(err.msg, message=err.payload['message']) 38 | elif err.__class__ is error.KepURLError: 39 | print(err.url) 40 | print(err.reason) 41 | messagebox.showerror('Error', message=err.msg) 42 | else: 43 | print(f'Different Exception Received: {err}') 44 | messagebox.showerror('Error', message=err) 45 | -------------------------------------------------------------------------------- /Config API/Config API Endpoint and Property Viewer/widgets.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | ''' 6 | Widget Classes 7 | ''' 8 | 9 | from tkinter import * 10 | from tkinter import ttk, messagebox 11 | 12 | 13 | class searchbar(ttk.Frame): 14 | def __init__(self, master=None, column=0, row=0, command = None): 15 | super().__init__(master) 16 | self.master = master 17 | self.grid(column=column, row=row, sticky=NSEW) 18 | self.searchInput = StringVar() 19 | self.input = Entry(self, textvariable= self.searchInput) 20 | self.input.grid(column=1, row=0, sticky=NSEW) 21 | self.button = Button(self, text='Search', command=command) 22 | self.button.grid(column=2, row=0, sticky=NSEW) 23 | self.columnconfigure(0, weight=1) 24 | self.columnconfigure(1, weight=1) 25 | self.columnconfigure(2, weight=0) 26 | self.rowconfigure(0, weight=1) 27 | self.input.event_add('<>', '') 28 | self.input.bind('<>', command) 29 | 30 | class vscroll(ttk.Scrollbar): 31 | def __init__(self, master=None, widget=None): 32 | super().__init__(master, orient=VERTICAL, command=widget.yview) 33 | self.master = master 34 | info = widget.grid_info() 35 | self.grid(column=info['column'] + 1, row=0, sticky=NSEW) 36 | 37 | class hscroll(ttk.Scrollbar): 38 | def __init__(self, master=None, widget=None): 39 | super().__init__(master, orient=HORIZONTAL, command=widget.xview) 40 | self.master = master 41 | info = widget.grid_info() 42 | self.grid(column=0, row=info['row'] + 1, sticky=NSEW) 43 | 44 | class treevscroll(ttk.Frame): 45 | def __init__(self, master=None, column=0, row=0, columns=None): 46 | super().__init__(master) 47 | self.master = master 48 | self.grid(column=column, row=row, sticky=NSEW) 49 | self.columnconfigure(0, weight=1) 50 | self.rowconfigure(0, weight=1) 51 | self.treev = ttk.Treeview(self, show='tree', columns = tuple(columns), displaycolumns=()) 52 | self.treev.column("#0", minwidth = 1000) 53 | self.treev.grid(column=0, row=0, sticky=NSEW) 54 | self.scrollbar = vscroll(self, self.treev) 55 | self.hscrollbar = hscroll(self,self.treev) 56 | self.treev.configure(yscrollcommand=self.scrollbar.set, xscrollcommand=self.hscrollbar.set) 57 | 58 | 59 | class textscroll(ttk.Frame): 60 | def __init__(self, master=None, column=0, row=0): 61 | super().__init__(master) 62 | self.master = master 63 | self.grid(column=column, row=row, sticky=NSEW) 64 | self.columnconfigure(0, weight=1) 65 | self.rowconfigure(0, weight=1) 66 | self.text = Text(self) 67 | self.text.grid(column=0, row=0, sticky=NSEW) 68 | self.scrollbar = vscroll(self,self.text) 69 | self.text.configure(yscrollcommand=self.scrollbar.set) 70 | 71 | 72 | 73 | # Update the whole text box 74 | def update(self, strings: list): 75 | self.text.config(state=NORMAL) 76 | self.text.delete(0.0, END) 77 | [self.text.insert(END, string, tags) for string, tags in strings] 78 | self.text.config(state=DISABLED) 79 | 80 | # Append data to the end of the existing content 81 | def append(self, string, tags= None): 82 | self.text.config(state=NORMAL) 83 | self.text.insert(END, string, tags) 84 | self.text.config(state=DISABLED) 85 | 86 | # Clear contents of text box 87 | def clear(self): 88 | self.text.config(state=NORMAL) 89 | self.text.delete(0.0, END) 90 | self.text.config(state=DISABLED) -------------------------------------------------------------------------------- /Config API/Convert Tags to Kepware CSV/README.md: -------------------------------------------------------------------------------- 1 | # Convert Tags from Config API to Kepware formatted CSV for Importing 2 | 3 | This script reads all tags and tag groups from a device via Config API and exports the Kepware formatted CSV for Tag importing through the Config GUI 4 | 5 | ## Install Python 6 | 7 | 1. Download [Python](https://www.python.org/downloads/) 8 | 2. During the install 9 | • Check box "Add python.exe to PATH" (can also be added later under "Advanced Options", check "Add Python to environment variables") 10 | • Select "Customized Installation" 11 | • Under "Optional Features", select "pip" 12 | 3. Install the [Kepware Config API SDK](https://github.com/PTCInc/Kepware-ConfigAPI-SDK-Python) library 13 | 14 | ## Enable the Configuration API 15 | 16 | 1. Right-Click on the Administration Tool (green icon in the system tray) and select Settings 17 | 2. Set Enable and Enable HTTP to Yes 18 | 3. Set CORS Allowed Origins to * 19 | 4. Set Enable Documentation to Yes 20 | 21 | ## Prepare Script 22 | 23 | 1. View [tag_export.py](tag_export.py) in a text editor 24 | 2. Set Kepware Server connection information in the "connection.server" method 25 | 3. Set output_file to the csv location 26 | 4. Set device_path to the channel.device location in the Kepware configuration 27 | 28 | ## Run the Script 29 | 30 | 1. Open a CMD and cd to the [tag_export.py](tag_export.py) location before launching the script or double-click on [tag_export.py](tag_export.py) from File Explorer 31 | -------------------------------------------------------------------------------- /Config API/Convert Tags to Kepware CSV/tag_export.py: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # ------------------------------------------------------------------------- 3 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 4 | # See License.txt in the project root for license information. 5 | # -------------------------------------------------------------------------- 6 | # Name: 7 | # tag_export.py 8 | # 9 | # Description: 10 | # This script reads all tags and tag groups from a device via Config API and exports 11 | # the Kepware formatted CSV for Tag importing through the Config GUI 12 | # 13 | # Procedure: 14 | # 1- Setup csv output and delete if previously existed 15 | # 2- iterates through all tag groups and exports the tags found in every tag group. 16 | # Dependencies: 17 | # Uses KepConfig package 18 | # 19 | # ******************************************************************************/ 20 | 21 | import kepconfig 22 | from kepconfig import connection, error 23 | from kepconfig.connectivity import channel, device, tag 24 | import json 25 | import csv 26 | import os 27 | import time 28 | 29 | # output file 30 | output_file = "MyDevice.csv" 31 | 32 | # device path 33 | device_path = "Channel1.Device1" 34 | 35 | # csv header and JSON mapping 36 | fields = ['Tag Name','Address','Data Type','Respect Data Type', 37 | 'Client Access','Scan Rate','Scaling','Raw Low','Raw High', 38 | 'Scaled Low','Scaled High','Scaled Data Type','Clamp Low', 39 | 'Clamp High','Eng Units','Description','Negate Value'] 40 | mapping = { 41 | "common.ALLTYPES_NAME": "Tag Name", 42 | "common.ALLTYPES_DESCRIPTION": "Description", 43 | "servermain.TAG_ADDRESS": "Address", 44 | "servermain.TAG_DATA_TYPE": { 45 | "id": "Data Type", 46 | "enumeration": { 47 | "Default": -1, 48 | "String": 0, 49 | "Boolean": 1, 50 | "Char": 2, 51 | "Byte": 3, 52 | "Short": 4, 53 | "Word": 5, 54 | "Long": 6, 55 | "DWord": 7, 56 | "Float": 8, 57 | "Double": 9, 58 | "BCD": 10, 59 | "LBCD": 11, 60 | "Date": 12, 61 | "LLong": 13, 62 | "QWord": 14, 63 | "String Array": 20, 64 | "Boolean Array": 21, 65 | "Char Array": 22, 66 | "Byte Array": 23, 67 | "Short Array": 24, 68 | "Word Array": 25, 69 | "Long Array": 26, 70 | "DWord Array": 27, 71 | "Float Array": 28, 72 | "Double Array": 29, 73 | "BCD Array": 30, 74 | "LBCD Array": 31, 75 | "Date Array": 32, 76 | "LLong Array": 33, 77 | "QWord Array": 34 78 | } 79 | }, 80 | "servermain.TAG_READ_WRITE_ACCESS": { 81 | "id":"Client Access", 82 | "enumeration": { 83 | "RO": 0, 84 | "R/W": 1 85 | } 86 | }, 87 | "servermain.TAG_SCAN_RATE_MILLISECONDS": "Scan Rate", 88 | "servermain.TAG_SCALING_TYPE": { 89 | "id": "Scaling", 90 | "enumeration": { 91 | "None": 0, 92 | "Linear": 1, 93 | "Square Root": 2 94 | } 95 | } 96 | } 97 | scaling_sub_mapping = { 98 | "servermain.TAG_SCALING_RAW_LOW": "Raw Low", 99 | "servermain.TAG_SCALING_RAW_HIGH": "Raw High", 100 | "servermain.TAG_SCALING_SCALED_DATA_TYPE": { 101 | "id":"Scaled Data Type", 102 | "enumeration": { 103 | "Char": 2, 104 | "Byte": 3, 105 | "Short": 4, 106 | "Word": 5, 107 | "Long": 6, 108 | "DWord": 7, 109 | "Float": 8, 110 | "Double": 9 111 | } 112 | }, 113 | "servermain.TAG_SCALING_SCALED_LOW": "Scaled Low", 114 | "servermain.TAG_SCALING_SCALED_HIGH": "Scaled High", 115 | "servermain.TAG_SCALING_CLAMP_LOW": "Clamp Low", 116 | "servermain.TAG_SCALING_CLAMP_HIGH": "Clamp High", 117 | "servermain.TAG_SCALING_NEGATE_VALUE": "Negate Value", 118 | "servermain.TAG_SCALING_UNITS": "Eng Units" 119 | } 120 | 121 | def HTTPErrorHandler(err): 122 | # Generic Handler for exception errors 123 | if err.__class__ is error.KepHTTPError: 124 | print(err.code) 125 | print(err.msg) 126 | print(err.url) 127 | print(err.hdrs) 128 | print(err.payload) 129 | elif err.__class__ is error.KepURLError: 130 | print(err.url) 131 | print(err.reason) 132 | else: 133 | print('Different Exception Received: {}'.format(err)) 134 | 135 | 136 | class CsvFormatter(): 137 | def __init__(self): 138 | self.output = open(output_file, mode= 'x', newline='') 139 | self.writer = csv.DictWriter(self.output, fields) 140 | 141 | def add_header(self): 142 | self.writer.writeheader() 143 | 144 | def write(self, record): 145 | self.writer.writerows(record) 146 | 147 | def close(self): 148 | return self.output.close() 149 | 150 | def logger_setup(): 151 | # delete file if exists 152 | try: 153 | os.remove(output_file) 154 | except OSError: 155 | pass 156 | 157 | logger = CsvFormatter() 158 | logger.add_header() 159 | return logger 160 | 161 | def log_tags(tags, current_path = ''): 162 | # Processes Tags list and logs to CSV 163 | ls = [] 164 | for tag in tags: 165 | output = {} 166 | # Filter out keys from Tag that are not related to scaling (General property group) 167 | s = set(mapping.keys()) 168 | non_scale_keys = [v for v in tag.keys() if v in s] 169 | 170 | for k in non_scale_keys: 171 | if k == 'common.ALLTYPES_NAME': 172 | name = tag[k] 173 | if current_path == '': 174 | output[mapping[k]] = name 175 | else: 176 | output[mapping[k]] = current_path + '.' + name 177 | elif k in ['servermain.TAG_DATA_TYPE','servermain.TAG_READ_WRITE_ACCESS']: 178 | key_list = list(mapping[k]['enumeration'].keys()) 179 | val_list = list(mapping[k]['enumeration'].values()) 180 | output[mapping[k]['id']] = key_list[val_list.index(tag[k])] 181 | elif k == 'servermain.TAG_SCALING_TYPE': 182 | if tag[k] == 0: 183 | output[mapping[k]['id']] = "" 184 | # Map all the scaling related properties to '' for no scaling configuration 185 | for scale in scaling_sub_mapping.keys(): 186 | if scale == 'servermain.TAG_SCALING_SCALED_DATA_TYPE': 187 | output[scaling_sub_mapping[scale]['id']] = "" 188 | else: 189 | output[scaling_sub_mapping[scale]] = "" 190 | else: 191 | # Map all the scaling related properties 192 | key_list = list(mapping[k]['enumeration'].keys()) 193 | val_list = list(mapping[k]['enumeration'].values()) 194 | output[mapping[k]['id']] = key_list[val_list.index(tag[k])] 195 | 196 | # Filter out keys from Tag that are related to scaling (Scaling property group) 197 | s_scale = set(scaling_sub_mapping.keys()) 198 | scale_keys = [v for v in tag.keys() if v in s_scale] 199 | for l in scale_keys: 200 | if l == 'servermain.TAG_SCALING_SCALED_DATA_TYPE': 201 | key_list = list(scaling_sub_mapping[l]['enumeration'].keys()) 202 | val_list = list(scaling_sub_mapping[l]['enumeration'].values()) 203 | output[scaling_sub_mapping[l]['id']] = key_list[val_list.index(tag[l])] 204 | elif l in ["servermain.TAG_SCALING_CLAMP_LOW","servermain.TAG_SCALING_CLAMP_HIGH", 205 | "servermain.TAG_SCALING_NEGATE_VALUE"]: 206 | output[scaling_sub_mapping[l]] = int(tag[l]) 207 | else: 208 | output[scaling_sub_mapping[l]] = tag[l] 209 | else: 210 | output[mapping[k]] = tag[k] 211 | # Append to list of CSV output 212 | ls.append(output) 213 | # Write to CSV file 214 | server_output.write(ls) 215 | 216 | def process_tags(current_path): 217 | tag_path = '' 218 | path_parts = kepconfig.path_split(current_path) 219 | try: 220 | for item in kepconfig.path_split(current_path)['tag_path']: 221 | tag_path = tag_path + '.' + item 222 | except KeyError: 223 | pass 224 | except: 225 | print('Other Error') 226 | 227 | try: 228 | tags = tag.get_all_tags(server, current_path) 229 | except Exception as err: 230 | # Retry if call fails 231 | HTTPErrorHandler(err) 232 | success = False 233 | retry = 1 234 | while retry != 3: 235 | time.sleep(1) 236 | try: 237 | tags = tag.get_all_tags(server, current_path) 238 | retry = 3 239 | success = True 240 | except Exception as err: 241 | retry = retry + 1 242 | HTTPErrorHandler(err) 243 | # If fails three times, move on 244 | if success == False: 245 | return False 246 | log_tags(tags, tag_path) 247 | 248 | try: 249 | tag_group = tag.get_all_tag_groups(server, current_path) 250 | except Exception as err: 251 | # Retry if call fails 252 | HTTPErrorHandler(err) 253 | success = False 254 | retry = 1 255 | while retry != 3: 256 | time.sleep(1) 257 | try: 258 | tag_group = tag.get_all_tag_groups(server, current_path) 259 | retry = 3 260 | success = True 261 | except Exception as err: 262 | retry = retry + 1 263 | HTTPErrorHandler(err) 264 | # If fails three times, move on 265 | if success == False: 266 | return False 267 | for group in tag_group: 268 | process_tags(current_path + '.' + group['common.ALLTYPES_NAME']) 269 | return True 270 | 271 | # Create output 272 | server_output = logger_setup() 273 | 274 | # Configure Kepware Server API connection 275 | server = connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '', https=False) 276 | 277 | dev = kepconfig.path_split(device_path) 278 | # output = {} 279 | current_path = '' 280 | time_start = time.perf_counter() 281 | 282 | process_tags(device_path) 283 | 284 | server_output.close() 285 | 286 | time_end = time.perf_counter() 287 | print('Complete {}! {} - Took {} seconds'.format(os.path.basename(__file__),time.asctime(), time_end - time_start)) 288 | 289 | -------------------------------------------------------------------------------- /Config API/Count Channels and Devices/README.md: -------------------------------------------------------------------------------- 1 | # Count the number of channels, devices, and channels by driver type in a running Kepware Server project 2 | 3 | ## Install Python and the Kepware Config API Python SDK 4 | 5 | 1. Download [Python](https://www.python.org/downloads/) 6 | 2. During the install 7 | • Check box "Add python.exe to PATH" (can also be added later under "Advanced Options", check "Add Python to environment variables") 8 | • Select "Customized Installation" 9 | • Under "Optional Features", select "pip" 10 | 3. Install the [Kepware Config API Python SDK](https://github.com/PTCInc/Kepware-ConfigAPI-SDK-Python) 11 | 12 | ## Enable the Kepware Server Configuration API 13 | 14 | 1. Right-Click on the Administration Tool (green icon in the system tray) and select Settings 15 | 2. Set Enable and Enable HTTP to Yes 16 | 3. Set CORS Allowed Origins to * 17 | 4. Set Enable Documentation to Yes 18 | 19 | ## Run the Script 20 | 21 | 1. Open a CMD and navigate to the location of [count_channels_and_devices.py](count_channels_and_devices.py) 22 | 2. Run the script (type 'python count_channels_and_devices.py' and hit enter) 23 | -------------------------------------------------------------------------------- /Config API/Count Channels and Devices/count_channels_and_devices.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for license information. 4 | # -------------------------------------------------------------------------- 5 | 6 | # Uses a target Kepware Server's Configuration API to count the number channels, 7 | # devices, and channels per driver type 8 | 9 | from collections import Counter 10 | from kepconfig import connection, error 11 | from kepconfig.connectivity import channel, device 12 | 13 | 14 | def discover_devices (channel): 15 | # Count all devices on a given channel 16 | devices_in_channel = 0 17 | try: 18 | # Use KepConfigAPI to get list of all devices for specified channel 19 | device_list = device.get_all_devices(server,channel) 20 | for i in device_list: 21 | devices_in_channel += 1 22 | return devices_in_channel 23 | except Exception as err: 24 | HTTPErrorHandler(err) 25 | 26 | 27 | def HTTPErrorHandler(err): 28 | # Generic Handler for exception errors 29 | if err.__class__ is error.KepHTTPError: 30 | print(err.code) 31 | print(err.msg) 32 | print(err.url) 33 | print(err.hdrs) 34 | print(err.payload) 35 | elif err.__class__ is error.KepURLError: 36 | print(err.url) 37 | print(err.reason) 38 | else: 39 | print('Different Exception Received: {}'.format(err)) 40 | 41 | 42 | # This creates a server reference that is used to target all modifications of the Kepware configuration 43 | server = connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '') 44 | 45 | # Define variables 46 | channel_count = 0 47 | device_count = 0 48 | idriver = [] 49 | 50 | # Get list of all channels in server project 51 | try: 52 | channel_list = channel.get_all_channels(server) 53 | except Exception as err: 54 | HTTPErrorHandler(err) 55 | 56 | # Get the driver type and device count per channel 57 | for i in channel_list: 58 | channel_count += 1 59 | channel_name = i['common.ALLTYPES_NAME'] 60 | # Add the driver type for this channel into our list 61 | idriver.append(i['servermain.MULTIPLE_TYPES_DEVICE_DRIVER']) 62 | # Call local discover_devices() to return counted devices per channel name and add to device counter 63 | try: 64 | device_count = device_count + discover_devices(channel_name) 65 | except Exception as err: 66 | HTTPErrorHandler(err) 67 | 68 | # Use Python Counter type to identify channel counts by driver 69 | driver_counts = Counter(idriver) 70 | 71 | # Format into Python dictionary type (json) 72 | driver_counts = dict(driver_counts) 73 | 74 | print("{} {} {} {} {} {}".format("Channel Count:", channel_count, ", Device Count:", device_count, ", Channel Count by Driver:", driver_counts)) 75 | -------------------------------------------------------------------------------- /Config API/Enable Secondary Server/README.md: -------------------------------------------------------------------------------- 1 | # Activate secondary server when primary is no longer responding 2 | 3 | This script is to be run on the secondary server and will disable or enable all supported features including devices, DataLogger groups, IoT Gateway Agents, Schedules and EFM Exporters when the primary server goes offline. The health of the primary is determined by checking the server information endpoint in the Configuration API, but a raw socket check can also be used. 4 | 5 | ## Install Python 6 | 7 | 1. Download [Python](https://www.python.org/downloads/) 8 | 2. During the install 9 | • Check box "Add python.exe to PATH" (can also be added later under "Advanced Options", check "Add Python to environment variables") 10 | • Select "Customized Installation" 11 | • Under "Optional Features", select "pip" 12 | 3. From a CMD, type ‘pip install kepconfig’ to install the [Kepconfig](https://github.com/PTCInc/Kepware-ConfigAPI-SDK-Python) package 13 | 14 | ## Enable the Configuration API on the Primary and Secondary servers 15 | 16 | 1. Right-Click on the Administration Tool (green icon in the system tray) and select Settings 17 | 2. Set Enable and Enable HTTP to Yes 18 | 3. Set CORS Allowed Origins to * 19 | 20 | ## Prepare Script 21 | 22 | 1. View [setup.json](setup.json) in a text editor 23 | 2. Set primary and secondary server Configuration API parameters 24 | 3. Set username and password for the API user 25 | 26 | ## Run the Script 27 | 28 | 1. Open a CMD and cd to the [standby.py](standby.py) location before launching the script or double-click on [standby.py](standby.py) from File Explorer 29 | -------------------------------------------------------------------------------- /Config API/Enable Secondary Server/setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "primaryHost" : "192.168.13.132", 3 | "primaryPort" : "57412", 4 | "secondaryHost" : "127.0.0.1", 5 | "secondaryPort" : "57412", 6 | "apiUsername" : "Administrator", 7 | "apiPassword" : "", 8 | "HTTPS": false, 9 | "trustAllCerts": true, 10 | "monitorInterval": 1 11 | } -------------------------------------------------------------------------------- /Config API/Enable Secondary Server/standby.py: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # ------------------------------------------------------------------------------ 3 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 4 | # See License.txt in the project root for license information. 5 | # ------------------------------------------------------------------------------ 6 | # 7 | # Description: 8 | # This script is to be run on the secondary Kepware server and will disable or enable all 9 | # supported features including devices, DataLogger groups, IoT Gateway Agents, 10 | # Schedules and EFM Exporters when the primary server goes offline. The health 11 | # of the primary is determined by verifying the port for the Configuration API is open, but a 12 | # raw socket can also be used. 13 | # 14 | # Requires: 15 | # Python on secondary Kepware server PC with Kepconfig package 16 | # 17 | # Update History: 18 | # 0.2.0: Updated to leverage Kepconfig package 19 | # Added HTTPS support 20 | # Use API call instead of port check for status validation 21 | # 22 | # Version: 0.2.0 23 | # ******************************************************************************/ 24 | 25 | import json 26 | import time 27 | import datetime 28 | import socket 29 | from kepconfig.connection import server 30 | from kepconfig.connectivity import channel, device 31 | from kepconfig.datalogger import log_group 32 | from kepconfig.iot_gateway import agent, MQTT_CLIENT_AGENT, REST_CLIENT_AGENT, REST_SERVER_AGENT 33 | 34 | import platform 35 | import subprocess 36 | 37 | def get_setup_parameters(path): 38 | try: 39 | print("Loading settings from {}".format(path)) 40 | with open(path) as j: 41 | data = json.load(j) 42 | return data 43 | except Exception as e: 44 | print("[Exception] Load failed - {}".format(e)) 45 | return False 46 | 47 | # Checks Socket of Config API service. Only provides status of port open, not running of 48 | # runtime or Config API service (since any app can open the port) 49 | def check_socket(server: server): 50 | host = server.host 51 | port = server.port 52 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 53 | s.settimeout(1) 54 | try: 55 | s.connect((host, int(port))) 56 | s.shutdown(socket.SHUT_RDWR) 57 | return True 58 | except: 59 | return False 60 | finally: 61 | s.close() 62 | 63 | # Check server status via API call, any non 200 response will throw an 64 | # KepHTTPerror or KepURLerror exception and is a sign that the config API service 65 | # or runtime service is not running 66 | # 67 | # Supported in Kepware v6.13 or later. Use check_socket() for older versions. 68 | def check_state(server: server): 69 | try: 70 | r = server.get_info() 71 | return True 72 | except: 73 | return False 74 | 75 | def objects(server: server, sState): 76 | #Check for Channels 77 | enableProperty = "servermain.DEVICE_DATA_COLLECTION" 78 | try: 79 | channelProperties = channel.get_all_channels(server) 80 | except: 81 | channelProperties = [] 82 | 83 | for eachChannel in channelProperties: 84 | thisChannel = eachChannel['common.ALLTYPES_NAME'] 85 | 86 | #Check for Devices within Channel 87 | try: 88 | deviceProperties = device.get_all_devices(server, thisChannel) 89 | except: 90 | deviceProperties = [] 91 | 92 | for eachDevice in deviceProperties: 93 | thisDevice = eachDevice['common.ALLTYPES_NAME'] 94 | try: 95 | result = device.modify_device(server, f'{thisChannel}.{thisDevice}', DATA={enableProperty: sState}, force=True) 96 | except Exception as e: 97 | print("[Exception] Failed to set property - {}".format(e)) 98 | 99 | #Check for DataLogger groups 100 | enableProperty = "datalogger.LOG_GROUP_ENABLED" 101 | try: 102 | logGroupProperties = log_group.get_all_log_groups(server) 103 | except: 104 | logGroupProperties = [] 105 | 106 | for eachLogGroup in logGroupProperties: 107 | eachLogGroup[enableProperty] = sState 108 | try: 109 | result = log_group.modify_log_group(server, DATA= eachLogGroup, force= True) 110 | except Exception as e: 111 | print("[Exception] Failed to set property - {}".format(e)) 112 | 113 | #Check for MQTT clients 114 | enableProperty = "iot_gateway.AGENTTYPES_ENABLED" 115 | try: 116 | agentProperties = agent.get_all_iot_agents(server, MQTT_CLIENT_AGENT) 117 | agentProperties.extend(agent.get_all_iot_agents(server, REST_CLIENT_AGENT)) 118 | agentProperties.extend(agent.get_all_iot_agents(server, REST_SERVER_AGENT)) 119 | except: 120 | agentProperties = [] 121 | 122 | for eachAgent in agentProperties: 123 | eachAgent[enableProperty] = sState 124 | try: 125 | result = agent.modify_iot_agent(server,eachAgent, force= True) 126 | except Exception as e: 127 | print("[Exception] Failed to set property - {}".format(e)) 128 | 129 | #Check for Schedules 130 | url = f"{server.url}/project/_scheduler/schedules" 131 | enableProperty = "scheduler.SCHEDULE_ENABLED" 132 | try: 133 | result = server._config_get(url) 134 | schedProperties = result.payload 135 | except: 136 | schedProperties = [] 137 | 138 | for eachSched in schedProperties: 139 | eachSched[enableProperty] = sState 140 | eachSched['FORCE_UPDATE'] = True 141 | try: 142 | result = server._config_update(f'{url}/{eachSched["common.ALLTYPES_NAME"]}', DATA= eachSched) 143 | except Exception as e: 144 | print("[Exception] Failed to set property - {}".format(e)) 145 | 146 | #Check for EFM exporters 147 | url = f"{server.url}/project/_efmexporter/poll_groups" 148 | enableProperty = "efm_exporter.POLLGROUP_ENABLED" 149 | 150 | try: 151 | result = server._config_get(url) 152 | pollGroupProperties = result.payload 153 | except: 154 | pollGroupProperties = [] 155 | 156 | for eachPollGroup in pollGroupProperties: 157 | eachPollGroup[enableProperty] = sState 158 | eachPollGroup['FORCE_UPDATE'] = True 159 | try: 160 | result = server._config_update(f'{url}/{eachPollGroup["common.ALLTYPES_NAME"]}', DATA= eachPollGroup) 161 | except Exception as e: 162 | print("[Exception] Failed to set property - {}".format(e)) 163 | 164 | return True 165 | 166 | if __name__ == "__main__": 167 | # load setup parameters 168 | setupData = get_setup_parameters('./Config API/Enable Secondary Server/setup.json') 169 | 170 | # assign global variables 171 | primaryHost = setupData['primaryHost'] 172 | primaryPort = setupData['primaryPort'] 173 | secondaryHost = setupData['secondaryHost'] 174 | secondaryPort = setupData['secondaryPort'] 175 | apiUsername = setupData['apiUsername'] 176 | apiPassword = setupData['apiPassword'] 177 | useHttps = setupData['HTTPS'] 178 | monitorInterval = setupData['monitorInterval'] 179 | 180 | # Create Server connection references to use 181 | primaryServer = server(primaryHost, primaryPort, apiUsername, apiPassword, https= useHttps) 182 | secondaryServer = server(secondaryHost, secondaryPort, apiUsername, apiPassword, https= useHttps) 183 | 184 | # Handle self-signed certs if needed 185 | if setupData['trustAllCerts']: 186 | primaryServer.SSL_trust_all_certs = True 187 | secondaryServer.SSL_trust_all_certs = True 188 | 189 | # be sure to use explicit operators (== or !=) to compare values since this is somewhat of a tri-state bool 190 | # none = unknown, true = objects are enabled, false = objects are disabled 191 | primaryState = None 192 | secondaryState = None 193 | primaryAvailable = None 194 | secondaryAvailable = None 195 | 196 | while True: 197 | # check health by connecting to the Configuration API's port and querying project properties 198 | # can be substituted with check_socket() if desired. 199 | # check_state() is supported in Kepware v6.13 or later. Use check_socket() for older versions. 200 | if check_state(primaryServer): 201 | if not primaryAvailable: 202 | print(f"{datetime.datetime.now()} - Primary is available") 203 | primaryAvailable = True 204 | else: 205 | if primaryAvailable: 206 | print(f"{datetime.datetime.now()} - [Warning] Primary is unavailable") 207 | primaryAvailable = False 208 | 209 | if check_state(secondaryServer): 210 | if not secondaryAvailable: 211 | print(f"{datetime.datetime.now()} - Secondary is available") 212 | secondaryAvailable = True 213 | else: 214 | if secondaryAvailable: 215 | print(f"{datetime.datetime.now()} - [Warning] Secondary not available - Please ensure Configuration API is enabled") 216 | secondaryAvailable = False 217 | 218 | # if connection is lost, assume state is unknown 219 | if not primaryAvailable: primaryState = None 220 | if not secondaryAvailable: secondaryState = None 221 | 222 | if primaryAvailable: 223 | # enable primary if needed 224 | if primaryState != True: 225 | result = objects(primaryServer,True) 226 | if result: 227 | primaryState = True 228 | print("{} - Primary has been enabled".format(datetime.datetime.now())) 229 | # only attempt to disable the secondary when it's available to avoid errors 230 | if secondaryAvailable and secondaryState != False: 231 | result = objects(secondaryServer,False) 232 | if result: 233 | secondaryState = False 234 | print("{} - Secondary has been disabled".format(datetime.datetime.now())) 235 | else: 236 | # enable secondary if needed. not necessary to disable primary since it's unavailabe 237 | if secondaryAvailable and secondaryState != True: 238 | result = objects(secondaryServer,True) 239 | if result: 240 | secondaryState = True 241 | print("{} - Secondary has been enabled".format(datetime.datetime.now())) 242 | pass 243 | 244 | time.sleep(monitorInterval) -------------------------------------------------------------------------------- /Config API/Get Event Log/GetEventLog.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------- 2 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 3 | # See License.txt in the project root for license information. 4 | # -------------------------------------------------------------------------- 5 | # 6 | # Name: GetEventLog.py 7 | # 8 | # Description: This script reads Kepware's Event Log through the Config API 9 | # and exports it to a .txt file in a desired folder. 10 | # 11 | # Dependencies: KepConfig package 12 | # Kepware Config API enabled 13 | # 14 | ############################################################################ 15 | 16 | 17 | from kepconfig import connection, error 18 | import json 19 | from contextlib import redirect_stdout 20 | import datetime 21 | 22 | 23 | # This creates a server reference that is used to target all modifications of the Kepware configuration 24 | server = connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '') 25 | 26 | # Global Variables - Update as needed 27 | 28 | # FiletoPath should include: an absolute path like "C:/Users/testUser/Destop" if file needs to be saved on the local machine 29 | # an UNC path "//servername/path" if the file needs to be saved on a different shared network drive 30 | DIR = "PathToFile" 31 | 32 | # length of time to look back in request - in Minutes 33 | INTERVAL = 30 34 | 35 | # Generic Handler for exception errors 36 | def HTTPErrorHandler(err): 37 | if err.__class__ is error.KepHTTPError: 38 | print(err.code) 39 | print(err.msg) 40 | print(err.url) 41 | print(err.hdrs) 42 | print(err.payload) 43 | elif err.__class__ is error.KepURLError: 44 | print(err.url) 45 | print(err.reason) 46 | else: 47 | print('Different Exception Received: {}'.format(err)) 48 | 49 | # Calculate a 30 minutes polling interval for the Event Log retrieval 50 | currentDate = datetime.datetime.now(datetime.UTC) 51 | hoursSubstracted = datetime.timedelta(minutes = INTERVAL) 52 | previousDate = currentDate - hoursSubstracted 53 | 54 | # Create a TKS Event Log file with date appended to the name 55 | fileName='TKS Log ' + datetime.datetime.now().strftime("%d-%m-%Y %H-%M-%S") 56 | filePath = DIR + fileName 57 | 58 | # This is needed so the script can be executed as a scheduled task 59 | 60 | 61 | # Write the content of the Event Log into a certain .txt file 62 | try: 63 | with open(filePath + '.txt', 'w') as f: 64 | with redirect_stdout(f): 65 | #Get the events from the past half hour - max number of events is 1000 66 | print('Event Log Poll Start Date (UTC):',previousDate,'---- Event Log Poll End Date (UTC):',currentDate) 67 | print("\n") 68 | print("{} - {}".format("Thingworx Kepware Server Event Log", json.dumps(server.get_event_log(1000, previousDate, datetime.datetime.now(datetime.UTC)), indent=4))) 69 | except Exception as err: 70 | HTTPErrorHandler(err) 71 | -------------------------------------------------------------------------------- /Config API/Get Event Log/README.md: -------------------------------------------------------------------------------- 1 | # Query Kepware's Event log for a certain period of time 2 | 3 | This script reads the Kepware event log via Config API and exports the last 30 minutes (default) of events to a text file. 4 | 5 | ## Install Python 6 | 7 | 1. Download [Python](https://www.python.org/downloads/) 8 | 2. During the install 9 | • Check box "Add python.exe to PATH" (can also be added later under "Advanced Options", check "Add Python to environment variables") 10 | • Select "Customized Installation" 11 | • Under "Optional Features", select "pip" 12 | 3. Install the [Kepware Config API SDK](https://github.com/PTCInc/Kepware-ConfigAPI-SDK-Python) library 13 | 14 | ## Enable the Configuration API 15 | 16 | 1. Right-Click on the Administration Tool (green icon in the system tray) and select Settings 17 | 2. Set Enable and Enable HTTP to Yes 18 | 3. Set Enable Documentation to Yes 19 | 20 | ## Prepare Script 21 | 22 | 1. View [GetEventLog.py](GetEventLog.py) in a text editor 23 | 2. Set Kepware Server connection information in the "connection.server" method 24 | 3. Set DIR variable to the directory location to output log files 25 | 4. Set INTERVAL variable to be the time to look back in the event log query. 26 | 27 | ## Run the Script 28 | 29 | 1. Open a CMD and cd to the [GetEventLog.py](GetEventLog.py) location before launching the script or double-click on [GetEventLog.py](GetEventLog.py) from File Explorer 30 | 31 | ## Optionally 32 | 33 | 1. Set up a scheduled task so the event log can be retrieved periodically based on the defined poll interval 34 | -------------------------------------------------------------------------------- /Config API/Project Synchronization/ProjectSync.py: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # ------------------------------------------------------------------------------ 3 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 4 | # See License.txt in the project root for license information. 5 | # ------------------------------------------------------------------------------ 6 | # 7 | # Description: 8 | # This script is run on the primary server to copy the entire project file to 9 | # a secondary server. The Python Paramiko library is used to SFTP the project 10 | # file over OpenSSH, but the script can be edited to use other file transfer 11 | # mechanisms instead. 12 | # 13 | # Requires: 14 | # -Python on Primary PC with Kepconfig SDK and Paramiko packages 15 | # -OpenSSH on Secondary PC (or other mechanism to copy a project from from the 16 | # Primary to the Secondary PC) 17 | # 18 | # Todo: 19 | # -Maybe check the registry for the correct location of ProgramData 20 | # -Add options for scheduling and/or project change detection 21 | # 22 | # Change History: 23 | # v0.2.0 - Updated to kepconfig SDK package for Config API handling and 24 | # removed Requests as requirement 25 | # 26 | # v0.2.0 27 | # ******************************************************************************/ 28 | 29 | import json 30 | import time 31 | import paramiko 32 | import os 33 | import kepconfig 34 | import kepconfig.error as error 35 | 36 | print(os.environ["ProgramData"]) 37 | 38 | def ErrorHandler(err): 39 | print("-- An error has occurred: ") 40 | # Generic Handler for exception errors 41 | if err.__class__ is error.KepError: 42 | print(err.msg) 43 | elif err.__class__ is error.KepHTTPError: 44 | print(err.code) 45 | print(err.msg) 46 | print(err.url) 47 | print(err.hdrs) 48 | print(err.payload) 49 | elif err.__class__ is error.KepURLError: 50 | print(err.url) 51 | print(err.reason) 52 | else: 53 | print('Different Exception Received: {}'.format(err)) 54 | 55 | def get_parameters(setup_file): 56 | try: 57 | print("Loading 'setup.json' from local directory") 58 | with open(setup_file) as j: 59 | setup_data = json.load(j) 60 | print("-- Setup load successful") 61 | return setup_data 62 | except Exception as e: 63 | print("-- Setup data failed to load - '{}'".format(e)) 64 | return False 65 | 66 | def save_source_project(server: kepconfig.connection.server): 67 | try: 68 | print("Saving project file on '{}'".format(server.host)) 69 | 70 | unixTime = str(time.time()).replace('.', '') #Use UNIX time to generate a unique filename 71 | fileName = "Project_{}.opf".format (unixTime) 72 | 73 | job = server.save_project('{}\\{}'.format(sourceFolder,fileName)) 74 | 75 | # check job status for completion 76 | while True: 77 | time.sleep(1) 78 | status = server.service_status(job) 79 | if (status.complete == True): break 80 | print("-- Project save successful - '\Kepware\KEPServerEX\V6\{}\\{}'".format(sourceFolder,fileName)) 81 | return fileName 82 | except Exception as e: 83 | print("-- Project failed to save --") 84 | ErrorHandler(e) 85 | return False 86 | 87 | def move_source_project(): 88 | 89 | try: 90 | print("Moving project file to '{}'".format(destination)) 91 | 92 | ssh = paramiko.SSHClient() 93 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 94 | ssh.connect(hostname=destination, username=sshUsername, password=sshPassword, port=22) 95 | sftp_client=ssh.open_sftp() 96 | 97 | #Probably should check the registry for the correct location of ProgramData 98 | fileIn = os.environ["ProgramData"] + "\\Kepware\\KEPServerEX\\V6\\{}\\{}".format (sourceFolder,projectName) 99 | 100 | fileOut = "{}\\{}".format (destinationFolder,projectName) 101 | 102 | sftp_client.put(fileIn,fileOut) 103 | sftp_client.close() 104 | ssh.close() 105 | 106 | print("-- Project move successful - '{}'".format(fileOut)) 107 | 108 | except Exception as e: 109 | print("-- Project move failed - '{}'".format(e)) 110 | return False 111 | 112 | def load_destination_project(server: kepconfig.connection.server): 113 | try: 114 | print("Loading project file on '{}'".format(destination)) 115 | 116 | job = server.load_project('{}\\{}'.format(destinationFolder,projectName)) 117 | 118 | # check job status for completion 119 | while True: 120 | time.sleep(1) 121 | status = server.service_status(job) 122 | if (status.complete == True): break 123 | print("-- Project load successful - '{}\\{}'".format(destinationFolder,projectName)) 124 | return 125 | except Exception as e: 126 | print("-- Project load failed --") 127 | ErrorHandler(e) 128 | return False 129 | 130 | # load setup parameters 131 | setupFilePath = './Config API/Project Synchronization/setup.json' 132 | setupData = get_parameters(setupFilePath) 133 | 134 | # assign global variables 135 | source = setupData['source'] 136 | sourcePort = setupData['sourcePort'] 137 | sourceFolder = setupData['sourceFolder'] 138 | destination = setupData['destination'] 139 | destinationPort = setupData['destinationPort'] 140 | destinationFolder = setupData ['destinationFolder'] 141 | apiUsername = setupData['apiUser'] 142 | apiPassword = setupData['apiPassword'] 143 | sshUsername = setupData['sshUser'] 144 | sshPassword = setupData['sshPassword'] 145 | 146 | server_source = kepconfig.connection.server(source, sourcePort, apiUsername, apiPassword, https=False) 147 | server_dest = kepconfig.connection.server(destination, destinationPort, apiUsername, apiPassword, https=False) 148 | 149 | # save source project 150 | projectName = save_source_project(server_source) 151 | if projectName is False: 152 | # TODO Failures 153 | pass 154 | 155 | # move project using sftc over ssh 156 | move_source_project() 157 | 158 | # load destination project 159 | load_destination_project(server_dest) 160 | 161 | print() 162 | print("Press any key...") 163 | input() 164 | -------------------------------------------------------------------------------- /Config API/Project Synchronization/README.md: -------------------------------------------------------------------------------- 1 | # Save, Transfer and Load Project File Between Two Kepware Servers 2 | 3 | This script is run on the primary server to copy the entire project file to a secondary server. The Python Paramiko library is used to SFTP the project file over OpenSSH, but the script can be edited to use other file transfer mechanisms instead. 4 | 5 | ## Install Python 6 | 7 | 1. Download [Python](https://www.python.org/downloads/) 8 | 2. During the install 9 | • Check box "Add python.exe to PATH" (can also be added later under "Advanced Options", check "Add Python to environment variables") 10 | • Select "Customized Installation" 11 | • Under "Optional Features", select "pip" 12 | 3. From a CMD, type ‘pip install kepconfig’ to install the [Kepware Config API SDK](https://github.com/PTCInc/Kepware-ConfigAPI-SDK-Python) library 13 | 4. From a CMD, type ‘pip install paramiko' to install the [Paramiko](https://www.paramiko.org/) library 14 | 15 | ## Enable the Configuration API on the Primary and Secondary servers 16 | 17 | 1. Right-Click on the Administration Tool (green icon in the system tray) and select Settings 18 | 2. Set Enable and Enable HTTP to Yes - script can be modified as necessary if HTTPS is desired 19 | 20 | ## Enable File Transfers on Secondary server 21 | 22 | 1. Install or Enable OpenSSH 23 | 2. Create a folder to receive the file transfer. For example, C:\Projects 24 | 3. Enable sharing to this folder 25 | 26 | ## Prepare Script 27 | 28 | 1. View [setup.json](setup.json) in a text editor 29 | 2. Set source, destination, ports and folder locations 30 | 3. Set username and password for the API and SSH users 31 | 32 | ## Run the Script 33 | 34 | 1. Open a CMD and cd to the [ProjectSync.py](ProjectSync.py) location before launching the script or double-click on [ProjectSync.py](ProjectSync.py) from File Explorer 35 | -------------------------------------------------------------------------------- /Config API/Project Synchronization/setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "source" : "LocalHost", 3 | "sourcePort": 57412, 4 | "sourceFolder" : "myFolder", 5 | "destination" : "", 6 | "destinationPort": 57412, 7 | "destinationFolder" : "C:\\Projects", 8 | "apiUser" : "Administrator", 9 | "apiPassword" : "", 10 | "sshUser" : "", 11 | "sshPassword" : "" 12 | } -------------------------------------------------------------------------------- /Config API/README.md: -------------------------------------------------------------------------------- 1 | # Configuration API Examples 2 | 3 | General Examples for interacting with the Kepware Configuraiton API. A more abstracted Python SDK package example is available here: [Kepware Config API Python SDK Example](https://github.com/PTCInc/Kepware-ConfigAPI-SDK-Python) 4 | 5 | Many other examples for each feature/Plug-in can be found in that features examples folder. 6 | -------------------------------------------------------------------------------- /Datalogger/CSV to DataLogger API/CSVtoDataLogger.py: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # ------------------------------------------------------------------------- 3 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 4 | # See License.txt in the project root for 5 | # license information. 6 | # -------------------------------------------------------------------------- 7 | # Description: 8 | # Created as a replacement for DataLogger's CSV Export/Import. This script 9 | # imports a CSV containing log items and POSTs them to the specified Log 10 | # Group using the Configuration API 11 | # 12 | # Procedure: 13 | # Read in setup file 14 | # Read in log item template 15 | # Read in CSV, convert to json 16 | # Create log items 17 | # 18 | # ******************************************************************************/ 19 | 20 | import csv 21 | import json 22 | import requests 23 | 24 | 25 | def get_parameters(setup_file): 26 | try: 27 | print("Loading 'setup.json' from local directory") 28 | with open(setup_file) as j: 29 | setup_data = json.load(j) 30 | print("-- Load succeeded") 31 | return setup_data 32 | except Exception as e: 33 | print("-- Load setup failed - '{}'".format(e)) 34 | return False 35 | 36 | def get_templates(): 37 | try: 38 | print("Loading Item JSON template from local directory") 39 | i_template = open('./objs/logitem.json') 40 | item = json.load(i_template) 41 | print("-- Load succeeded") 42 | return item 43 | except Exception as e: 44 | print("-- Load failed - '{}'".format(e)) 45 | return False 46 | 47 | def convert_csv_to_json(path): 48 | try: 49 | print("Converting CSV at '{}' into JSON".format(path)) 50 | csv_file = open(path, 'r') 51 | reader = csv.DictReader(csv_file) 52 | out = json.dumps([row for row in reader]) 53 | json_from_csv = json.loads(out) 54 | print("-- Conversion succeeded") 55 | return json_from_csv 56 | except Exception as e: 57 | print("-- Conversion failed - '{}'".format (e)) 58 | return False 59 | 60 | def log_group_enabled(state): 61 | try: 62 | print("Changing state of {} to Enabled = {}".format(group,state)) 63 | 64 | url = "http://127.0.0.1:57412/config/v1/project/_datalogger/log_groups/{}".format (group) 65 | payload = "{{\"FORCE_UPDATE\": true,\"datalogger.LOG_GROUP_ENABLED\": {}}}".format (state) 66 | response = requests.put(url, auth=(username, password), data=(payload)) 67 | 68 | if response: 69 | print("-- State change succeeded") 70 | else: 71 | print("-- An error has occurred: " + str(response.status_code)) 72 | 73 | except Exception as e: 74 | print("-- State change failed - '{}'".format (e)) 75 | return False 76 | 77 | def add_items_to_loggroup(list,template): 78 | try: 79 | print() 80 | print("Attempting to add {} items to {}".format ((len(list)),group)) 81 | url = "http://127.0.0.1:57412/config/v1/project/_datalogger/log_groups/{}/log_items".format (group) 82 | 83 | for item in list: 84 | template['common.ALLTYPES_NAME'] = item['Item_Name'] 85 | template['common.ALLTYPES_DESCRIPTION'] = item['Description'] 86 | template['datalogger.LOG_ITEM_ID'] = item['Item_ID'] 87 | template['datalogger.LOG_ITEM_NUMERIC_ID'] = item['Numeric_Alias'] 88 | template['datalogger.LOG_ITEM_DATA_TYPE'] = item['Data_Type'] 89 | template['datalogger.LOG_ITEM_DEADBAND_TYPE'] = int(item['Deadband_Type']) 90 | template['datalogger.LOG_ITEM_DEADBAND_VALUE'] = int(item['Deadband']) 91 | template['datalogger.LOG_ITEM_DEADBAND_LO_RANGE'] = int(item['Range_Low']) 92 | template['datalogger.LOG_ITEM_DEADBAND_HI_RANGE'] = int(item['Range_High']) 93 | payload = json.dumps(template) 94 | 95 | response = requests.post(url, auth=(username, password), data=(payload)) 96 | 97 | message = item['Item_Name'] + " = " 98 | if response: 99 | message = message + "Success" 100 | else: 101 | message = message + "An error has occurred: " + str(response.status_code) 102 | print(message) 103 | except Exception as e: 104 | print("-- Add failed - '{}'".format (e)) 105 | return False 106 | 107 | # load setup parameters 108 | setupFilePath = 'setup.json' 109 | setupData = get_parameters(setupFilePath) 110 | 111 | # assign global variables 112 | group = setupData['logGroupName'] 113 | path = setupData['importPath'] 114 | username = setupData['apiUser'] 115 | password = setupData['apiPassword'] 116 | 117 | # get json template for item 118 | itemTemplate = get_templates() 119 | 120 | # convert CSV file to JSON 121 | itemList = convert_csv_to_json(path) 122 | 123 | # disable log group. items can be added to an enabled log group via the API, but let's disable the group anyway 124 | log_group_enabled('false') 125 | 126 | # add tags to log group 127 | add_items_to_loggroup(itemList,itemTemplate) 128 | 129 | # enable log group 130 | print() 131 | log_group_enabled('true') 132 | 133 | print() 134 | print("Press any key...") 135 | input() 136 | -------------------------------------------------------------------------------- /Datalogger/CSV to DataLogger API/README.md: -------------------------------------------------------------------------------- 1 | # Import Datalogger Log Items from CSV using the Configuration API 2 | 3 | ## Install Python 4 | 5 | 1. Download [Python](https://www.python.org/downloads/) 6 | 2. During the install 7 | • Check box "Add python.exe to PATH" (can also be added later under "Advanced Options", check "Add Python to environment variables") 8 | • Select "Customized Installation" 9 | • Under "Optional Features", select "pip" 10 | 3. From a CMD, type ‘pip install requests’ to install the [Requests](https://2.python-requests.org/en/master/) library 11 | 12 | ## Enable the Configuration API 13 | 14 | 1. Right-Click on the Administration Tool (green icon in the system tray) and select Settings 15 | 2. Set Enable and Enable HTTP to Yes 16 | 3. Set CORS Allowed Origins to * 17 | 4. Set Enable Documentation to Yes 18 | 19 | ## Prepare Script 20 | 21 | 1. View [..\setup.json](setup.json) in a text editor 22 | 2. Set logGroupName to the name of the Log Group in KEPServerEX 23 | 3. Set importPath to the csv location 24 | 4. Set apiUser and apiPassword to the user and password. If an Administrator password was created during the KEPServerEX install, enter it here 25 | 5. View [..\csvs\logitems_example.csv](\csvs\logitems_example.csv) in a text editor 26 | 6. Edit Log Items as needed. Tags must exist on the server 27 | 28 | ## Run the Script 29 | 30 | 1. Open a CMD and cd to the [CSVtoDataLogger.py](CSVtoDataLogger.py) location before launching the script or double-click on [..\CSVtoDataLogger.py](CSVtoDataLogger.py) from File Explorer 31 | 2. If an item fails to get added, a HTTP 400 error will appear 32 | -------------------------------------------------------------------------------- /Datalogger/CSV to DataLogger API/csvs/logitems_example.csv: -------------------------------------------------------------------------------- 1 | Item_Name,Item_ID,Numeric_Alias,Data_Type,Deadband_Type,Deadband,Range_Low,Range_High,Description 2 | Tag1,Channel1.Device1.Tag1,0,Word,0,0,0,0,My Description for Item1 3 | Tag2,Channel1.Device1.Tag2,1,Word,0,0,0,0,My Description for Item2 4 | -------------------------------------------------------------------------------- /Datalogger/CSV to DataLogger API/objs/logitem.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "LogItem1", 3 | "common.ALLTYPES_DESCRIPTION": "", 4 | "datalogger.LOG_ITEM_ID": "Channel1.Device1.Tag1", 5 | "datalogger.LOG_ITEM_NUMERIC_ID": "0", 6 | "datalogger.LOG_ITEM_DATA_TYPE": "Float", 7 | "datalogger.LOG_ITEM_DEADBAND_TYPE": 0, 8 | "datalogger.LOG_ITEM_DEADBAND_VALUE": 0, 9 | "datalogger.LOG_ITEM_DEADBAND_LO_RANGE": 0, 10 | "datalogger.LOG_ITEM_DEADBAND_HI_RANGE": 0 11 | } -------------------------------------------------------------------------------- /Datalogger/CSV to DataLogger API/setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "logGroupName" : "LogGroup1", 3 | "importPath" : "csvs/logitems_example.csv", 4 | "apiUser" : "Administrator", 5 | "apiPassword" : "" 6 | } -------------------------------------------------------------------------------- /Datalogger/DataLogger to CSV/DataLoggerToCSV.py: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # ------------------------------------------------------------------------- 3 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 4 | # See License.txt in the project root for license information. 5 | # -------------------------------------------------------------------------- 6 | # Description: 7 | # Created as a replacement for DataLogger's CSV Export. This script uses 8 | # the Configuration API to export a the specified log group to a CSV file 9 | # that can be edited and re-imported using the CSVtoDataLogger script. 10 | # exports a log group to CSV where it can be edited and re-imported. 11 | # 12 | # Procedure: 13 | # Read in setup file 14 | # Get log group items 15 | # Write log items to CSV 16 | # 17 | # ******************************************************************************/ 18 | 19 | import json 20 | import requests 21 | 22 | 23 | def get_parameters(setup_file): 24 | try: 25 | print("Loading 'setup.json' from local directory") 26 | with open(setup_file) as j: 27 | setup_data = json.load(j) 28 | print("-- Load succeeded") 29 | return setup_data 30 | except Exception as e: 31 | print("-- Load setup failed - '{}'".format(e)) 32 | return False 33 | 34 | def get_log_group_items(groupName): 35 | 36 | try: 37 | print("Getting log group items for '{}'".format(groupName)) 38 | 39 | url = "http://127.0.0.1:57412/config/v1/project/_datalogger/log_groups/{}/log_items".format (group) 40 | response = requests.get(url, auth=(username, password)) 41 | data = json.loads(response.text) 42 | print("-- Get succeeded") 43 | return data 44 | except Exception as e: 45 | print("-- Get failed - '{}'".format (e)) 46 | return False 47 | 48 | def write_to_csv(data,path): 49 | try: 50 | print("Writing CSV to '{}'".format(path)) 51 | 52 | file = open(path, 'w') 53 | file.write("Item_Name,Item_ID,Numeric_Alias,Data_Type,Deadband_Type,Deadband,Range_Low,Range_High,Description") 54 | file.write("\n") 55 | 56 | for each in data: 57 | file.write(each['common.ALLTYPES_NAME'] 58 | + "," + each['datalogger.LOG_ITEM_ID'] + "," 59 | + str(each['datalogger.LOG_ITEM_NUMERIC_ID']) + "," 60 | + str(each['datalogger.LOG_ITEM_DATA_TYPE']) + "," 61 | + str(each['datalogger.LOG_ITEM_DEADBAND_TYPE']) + "," 62 | + str(each['datalogger.LOG_ITEM_DEADBAND_VALUE']) + "," 63 | + str(each['datalogger.LOG_ITEM_DEADBAND_LO_RANGE']) + "," 64 | + str(each['datalogger.LOG_ITEM_DEADBAND_HI_RANGE']) + "," 65 | + each['common.ALLTYPES_DESCRIPTION']) 66 | #must handle last row here 67 | file.write("\n") #must handle last row here 68 | print("-- Write succeeded") 69 | 70 | except Exception as e: 71 | print("-- Write failed - '{}'".format (e)) 72 | return False 73 | finally: 74 | file.close() 75 | 76 | # load setup parameters 77 | setupFilePath = 'setup.json' 78 | setupData = get_parameters(setupFilePath) 79 | 80 | # assign global variables 81 | group = setupData['logGroupName'] 82 | path = setupData['exportPath'] 83 | username = setupData['apiUser'] 84 | password = setupData['apiPassword'] 85 | 86 | # get log group items 87 | logItemData = get_log_group_items(group) 88 | 89 | # write to CSV 90 | write_to_csv(logItemData,path) 91 | 92 | print() 93 | print("Press any key...") 94 | input() 95 | -------------------------------------------------------------------------------- /Datalogger/DataLogger to CSV/README.md: -------------------------------------------------------------------------------- 1 | # Export Datalogger Log Items to CSV using the Configuration API 2 | 3 | ## Install Python 4 | 5 | 1. Download [Python](https://www.python.org/downloads/) 6 | 2. During the install 7 | • Check box "Add python.exe to PATH" (can also be added later under "Advanced Options", check "Add Python to environment variables") 8 | • Select "Customized Installation" 9 | • Under "Optional Features", select "pip" 10 | 3. From a CMD, type ‘pip install requests’ to install the [Requests](https://2.python-requests.org/en/master/) library 11 | 12 | ## Enable the Configuration API 13 | 14 | 1. Right-Click on the Administration Tool (green icon in the system tray) and select Settings 15 | 2. Set Enable and Enable HTTP to Yes 16 | 3. Set CORS Allowed Origins to * 17 | 4. Set Enable Documentation to Yes 18 | 19 | ## Prepare Script 20 | 21 | 1. View [setup.json](setup.json) in a text editor 22 | 2. Set logGroupName to the name of the Log Group in KEPServerEX 23 | 3. Set exportPath to the csv location and filename 24 | 4. Set apiUser and apiPassword to the user and password. If an Administrator password was created during the KEPServerEX install, enter it here 25 | 26 | ## Run the Script 27 | 28 | 1. Open a CMD and cd to the [DataLoggerToCSV.py](DataLoggerToCSV.py) location before launching the script or double-click on [DataLoggerToCSV.py](DataLoggerToCSV.py) from File Explorer 29 | 2. Once complete, a csv will appear in the location specified in the [setup.json](setup.json) file 30 | -------------------------------------------------------------------------------- /Datalogger/DataLogger to CSV/csvs/logitems_export.csv: -------------------------------------------------------------------------------- 1 | Item_Name,Item_ID,Numeric_Alias,Data_Type,Deadband_Type,Deadband,Range_Low,Range_High,Description 2 | LogItem1,Simulation Examples.Functions.Ramp1,0,Long,0,0,0,0, 3 | LogItem10,Simulation Examples.Functions.Random2,0,Word,0,0,0,0, 4 | LogItem11,Simulation Examples.Functions.Random3,0,Long,0,0,0,0, 5 | LogItem12,Simulation Examples.Functions.Random4,0,Long,0,0,0,0, 6 | LogItem13,Simulation Examples.Functions.Random5,0,Long,0,0,0,0, 7 | LogItem14,Simulation Examples.Functions.Random6,0,LLong,0,0,0,0, 8 | LogItem15,Simulation Examples.Functions.Random7,0,QWord,0,0,0,0, 9 | LogItem16,Simulation Examples.Functions.Random8,0,QWord,0,0,0,0, 10 | LogItem17,Simulation Examples.Functions.Sine1,0,Float,0,0,0,0, 11 | LogItem18,Simulation Examples.Functions.Sine2,0,Float,0,0,0,0, 12 | LogItem19,Simulation Examples.Functions.Sine3,0,Float,0,0,0,0, 13 | LogItem2,Simulation Examples.Functions.Ramp2,0,Float,0,0,0,0, 14 | LogItem20,Simulation Examples.Functions.Sine4,0,Float,0,0,0,0, 15 | LogItem21,Simulation Examples.Functions.User1,0,String,0,0,0,0, 16 | LogItem22,Simulation Examples.Functions.User2,0,Float,0,0,0,0, 17 | LogItem23,Simulation Examples.Functions.User3,0,Boolean,0,0,0,0, 18 | LogItem24,Simulation Examples.Functions.User4,0,String,0,0,0,0, 19 | LogItem3,Simulation Examples.Functions.Ramp3,0,Word,0,0,0,0, 20 | LogItem4,Simulation Examples.Functions.Ramp4,0,Long,0,0,0,0, 21 | LogItem5,Simulation Examples.Functions.Ramp5,0,LLong,0,0,0,0, 22 | LogItem6,Simulation Examples.Functions.Ramp6,0,QWord,0,0,0,0, 23 | LogItem7,Simulation Examples.Functions.Ramp7,0,LLong,0,0,0,0, 24 | LogItem8,Simulation Examples.Functions.Ramp8,0,Double,0,0,0,0, 25 | LogItem9,Simulation Examples.Functions.Random1,0,Short,0,0,0,0,DataLogger description 26 | Tag1,Channel1.Device1.Tag1,0,Word,0,0,0,0,My Description for Item1 27 | Tag2,Channel1.Device1.Tag2,1,Word,0,0,0,0,My Description for Item2 28 | -------------------------------------------------------------------------------- /Datalogger/DataLogger to CSV/setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "logGroupName" : "LogGroup1", 3 | "exportPath" : "csvs/logitems_export.csv", 4 | "apiUser" : "Administrator", 5 | "apiPassword" : "" 6 | } -------------------------------------------------------------------------------- /Datalogger/Powershell/Add Tags from CSV to Log Group/addLogItemstoLogGroup.ps1: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # This is script is for example and instructional purposes only. 3 | # 4 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 5 | # See License.txt in the project root for license information. 6 | # 7 | # Name: 8 | # - Add tags to Log Group 9 | # 10 | # Purpose: 11 | # - Add a set of tags within a CSV file to a target Log Group 12 | # 13 | # To use: 14 | # - Update 'csvs/deviceItems.csv' with desired set of tag paths and tag names (see Notes section below) 15 | # - Update 'csvs/logGroup.csv' with target Log Group 16 | # - Update 'csvs/auth.csv' with Kepware Username and Password for use with the Config API 17 | # 18 | # Notes: 19 | # - To quickly get started, export a device or folder's tag set to a CSV file using the GUI or via an API script and add a column called "Tag Path"; 20 | # within the Tag Path column, place the tag's location within the server project with the following format: 'channel.device..' 21 | # 22 | # Requirements: 23 | # - Log Group must be pre-created in DataLogger 24 | # - If not already enabled, enable Kepware Server Configuration API service (consult Kepware Server documentation for details) 25 | # - As necessary, edit the script's configuration to target correct Kepware Server IP address and Config API service port 26 | # ******************************************************************************/ 27 | 28 | Function Get-Header { 29 | $csv1 = $PSScriptRoot + "/csvs/auth.csv" 30 | $auth = Import-Csv $csv1 31 | $username = $auth.'Username' 32 | $password = $auth.'Password' 33 | $credPair = "$($username):$($password)" 34 | $encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("$credPair")) 35 | $headers = @{ Authorization = "Basic $encodedCredentials" } 36 | Write-Output ($headers) 37 | } 38 | 39 | Function Get-DeviceItems { 40 | $csv = $PSScriptRoot + '\csvs\deviceItems.csv' 41 | $obj = Import-Csv $csv 42 | Return ($obj) 43 | } 44 | Function Get-LogGroup{ 45 | $csv1 = $PSScriptRoot + '\csvs\logGroup.csv' 46 | $obj = Import-Csv $csv1 47 | Return ($obj) 48 | } 49 | 50 | Function Get-LogItemTemplate { 51 | $lItem = @' 52 | { 53 | "common.ALLTYPES_NAME": "", 54 | "common.ALLTYPES_DESCRIPTION": "", 55 | "datalogger.LOG_ITEM_ID": "", 56 | "datalogger.LOG_ITEM_NUMERIC_ID": "0", 57 | "datalogger.LOG_ITEM_DATA_TYPE": "", 58 | "datalogger.LOG_ITEM_DEADBAND_TYPE": 0, 59 | "datalogger.LOG_ITEM_DEADBAND_VALUE": 0, 60 | "datalogger.LOG_ITEM_DEADBAND_LO_RANGE": 0, 61 | "datalogger.LOG_ITEM_DEADBAND_HI_RANGE": 0 62 | } 63 | '@ | ConvertFrom-Json 64 | 65 | Return $lItem 66 | } 67 | Function Add-LogItems ($param1) { 68 | $logGroup = Get-LogGroup 69 | $logItemJson = ConvertTo-Json $param1 70 | $uriLogItems = "http://127.0.0.1:57412/config/v1/project/_datalogger/log_groups/$($logGroup.Log_Group)/log_items" 71 | Invoke-RestMethod -Uri $uriLogItems -Method 'Post' -Headers (Get-Header) -Body $logItemJson 72 | } 73 | 74 | Function Get-LogItemArray { 75 | $deviceItems = Get-DeviceItems 76 | $logItemObj = @() 77 | $logItem = Get-LogItemTemplate 78 | foreach ($i in $deviceItems) { 79 | $_itemPath = $i.'Tag Path' -replace '\.', '-' 80 | $logItem.'common.ALLTYPES_NAME' = $_itemPath + $i."Tag Name" 81 | $logItem."datalogger.LOG_ITEM_ID" = $i.'Tag Path' + $i.'Tag Name' 82 | $logItem."datalogger.LOG_ITEM_DATA_TYPE" = $i.'Data Type' 83 | $logItemObj += $logItem.psobject.copy() 84 | } 85 | Return $logItemObj 86 | } 87 | 88 | $logItemArray = Get-LogItemArray 89 | Add-LogItems ($logItemArray) 90 | 91 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Add Tags from CSV to Log Group/csvs/auth.csv: -------------------------------------------------------------------------------- 1 | UserName,Password 2 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Add Tags from CSV to Log Group/csvs/deviceItems.csv: -------------------------------------------------------------------------------- 1 | Tag Path,Tag Name,Address,Data Type,Respect Data Type,Client Access,Scan Rate,Scaling,Raw Low,Raw High,Scaled Low,Scaled High,Scaled Data Type,Clamp Low,Clamp High,Eng Units,Description,Negate Value 2 | Channel1.Device1.,Tag1,,Word,1,R/W,100,,,,,,,,,,, 3 | Channel1.Device1.,Tag2,,,,,,,,,,,,,,,, 4 | Channel1.Device1.,Tag3,,,,,,,,,,,,,,,, 5 | Channel1.Device1.,Tag4,,,,,,,,,,,,,,,, 6 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Add Tags from CSV to Log Group/csvs/logGroup.csv: -------------------------------------------------------------------------------- 1 | Log_Group 2 | LogGroup1 3 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Add Tags from CSV to Log Group/readme.md: -------------------------------------------------------------------------------- 1 | # Add tags from CSV to Log Group 2 | 3 | ## Purpose: 4 | - Add a set of tags within a CSV file to a target Log Group 5 | 6 | ## To use: 7 | - Update [csvs/deviceItems.csv](csvs/deviceItems.csv) with desired set of tag paths and tag names (see Notes section below) 8 | - Update [csvs/logGroup.csv](csvs/logGroup.csv) with target Log Group 9 | - Update [csvs/auth.csv](csvs/logGroup.csv) with Kepware Username and Password for use with the Config API 10 | 11 | ## Notes: 12 | - To quickly get started, export a device or folder's tag set to a CSV file using the GUI or via an API script and add a column called "Tag Path"; 13 | within the Tag Path column, place the tag's location within the server project with the following format: 'channel.device..' 14 | 15 | ## Requirements: 16 | - Log Group must be pre-created in DataLogger 17 | - If not already enabled, enable Kepware Server Configuration API service (consult Kepware Server documentation for details) 18 | - As necessary, edit the script's configuration to target correct Kepware Server IP address and Config API service port 19 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Export Log Items to CSV/csvs/auth.csv: -------------------------------------------------------------------------------- 1 | UserName,Password 2 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Export Log Items to CSV/csvs/logGroup.csv: -------------------------------------------------------------------------------- 1 | Log_Group 2 | LogGroup1 3 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Export Log Items to CSV/exportLogItemsToCsv.ps1: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # ------------------------------------------------------------------------- 3 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 4 | # See License.txt in the project root for license information.Name: 5 | # 6 | # - Export log items to CSV 7 | # 8 | # Purpose: 9 | # - For a given Log Group, export all added log items to a CSV file 10 | # 11 | # To use: 12 | # - Update 'csvs/logGroup.csv' with target Log Group name 13 | # - Update 'auth' with Kepware Config API username and password 14 | # 15 | # Requirements: 16 | # - Database table must be pre-created and Log Group must be configured to Log to Existing Table 17 | # - Kepware Configuration API must be enabled for HTTP 18 | # - As necessary, edit the script's configuration to target correct Kepware Server IP address and Config API service port 19 | # ******************************************************************************/ 20 | 21 | Function Get-Auth { 22 | $csv1 = $PSScriptRoot + "/csvs/auth.csv" 23 | $auth = Import-Csv $csv1 24 | Return ($auth) 25 | } 26 | 27 | Function Get-Header ($param) { 28 | $username = $param.'Username' 29 | $password = $param.'Password' 30 | $credPair = "$($username):$($password)" 31 | $encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("$credPair")) 32 | $headers = @{ Authorization = "Basic $encodedCredentials" } 33 | Return ($headers) 34 | } 35 | 36 | Function Get-LogGroup { 37 | $csv1 = $PSScriptRoot + '\csvs\logGroup.csv' 38 | $obj = Import-Csv $csv1 39 | Return ($obj) 40 | } 41 | 42 | Function Get-LogItems ($param1) { 43 | $path = "http://127.0.0.1:57412/config/v1/project/_datalogger/log_groups/$($logGroup.Log_Group)/log_items/" 44 | $obj = Invoke-RestMethod -Uri $path -Method 'Get' -Headers $headers 45 | Return ($obj) 46 | } 47 | 48 | Function Export-LogItems ($param) { 49 | $path = $PSScriptRoot + "/csvs/exportedLogItems.csv" 50 | $param | Select-Object common.ALLTYPES_NAME, datalogger.LOG_ITEM_ID, datalogger.LOG_ITEM_NUMERIC_ID, datalogger.LOG_ITEM_DATA_TYPE, datalogger.LOG_ITEM_DEADBAND_TYPE, datalogger.LOG_ITEM_DEADBAND_VALUE, datalogger.LOG_ITEM_DEADBAND_LO_RANGE, datalogger.LOG_ITEM_DEADBAND_HI_RANGE | ConvertTo-csv > $path -NoTypeInformation 51 | $path = $PSScriptRoot + "/csvs/exportedLogItems.csv" 52 | } 53 | 54 | $auth = Get-Auth 55 | $headers = Get-Header ($auth) 56 | $logGroup = Get-LogGroup 57 | $logItems = Get-LogItems ($headers) 58 | Export-LogItems ($logItems) -------------------------------------------------------------------------------- /Datalogger/Powershell/Export Log Items to CSV/readme.md: -------------------------------------------------------------------------------- 1 | # Export Log Items to a CSV file 2 | 3 | ## Purpose: 4 | - For a given Log Group, export all Log Items (i.e. tag references) to a CSV file 5 | 6 | ## To use: 7 | - Update [csvs/logGroup.csv](csvs/logGroup.csv) with target Log Group name 8 | - Update [csvs/auth.csv](csvs/auth.csv) with Kepware Config API username and password 9 | 10 | ## Requirements: 11 | - Kepware Configuration API must be enabled for HTTP 12 | - As necessary, edit the script's configuration to target correct Kepware Server IP address and Config API service port 13 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Use CSV to Configure Column Mappings/configureColumnMappings.ps1: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # This is script is for example and instructional purposes only. 3 | # 4 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 5 | # See License.txt in the project root for license information. 6 | # 7 | # Name: 8 | # Configure wide format column mappings 9 | # 10 | # Purpose: 11 | # - For a given Log Group, automatically configure tag-to-column mappings using a source CSV file 12 | # 13 | # To use: 14 | # - Add desired tags to Log Group using GUI or API 15 | # - Update 'csvs/logGroup.csv' with target Log Group 16 | # - Update 'csvs/columnMappings.csv' with desired tag mapping scheme, data types and lengths 17 | # - Update 'auth' with Kepware Config API username and password 18 | # 19 | # Notes: 20 | # - To quickly and easily source full tag paths (chan.dev..name) of log items with column mappings desired for custom configuration, run the helper script "exportLogItemsToCsv.ps1" and copy the "datalogger.LOG_ITEM_ID" column from the exported CSV into the "Log_Item_ID" column of the columnMappings.csv file. 21 | # - SQL Data Type Enumerations and (Lengths) 22 | # --- Integer = 7 (4) 23 | # --- Real = 4 (4) 24 | # --- Char = 1 (64) 25 | # --- Timestamp = 11 (16) 26 | # --- Smallint = 5 (2) 27 | # 28 | # Requirements: 29 | # - Database table must be pre-created and Log Group must be configured to Log to Existing Table 30 | # - Wide Format must be used 31 | # - If not already enabled, enable Kepware Server Configuration API service (consult Kepware Server documentation for details) 32 | # - As necessary, edit the script's configuration to target correct Kepware Server IP address and Config API service port 33 | # ******************************************************************************/ 34 | 35 | Function Get-Header { 36 | $csv1 = $PSScriptRoot + "/csvs/auth.csv" 37 | $auth = Import-Csv $csv1 38 | $username = $auth.'UserName' 39 | $password = $auth.'Password' 40 | $credPair = "$($username):$($password)" 41 | $encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("$credPair")) 42 | $headers = @{ Authorization = "Basic $encodedCredentials" } 43 | Write-Output ($headers) 44 | } 45 | 46 | Function Get-LogGroup{ 47 | $csv1 = $PSScriptRoot + '\csvs\logGroup.csv' 48 | $obj = Import-Csv $csv1 49 | Return ($obj) 50 | } 51 | 52 | Function Get-ColumnMappings { 53 | $csv1 = $PSScriptRoot + '\csvs\columnMappings.csv' 54 | $obj = Import-Csv $csv1 55 | Return ($obj) 56 | } 57 | 58 | Function Get-KepColumnMappingDefaults ($param1) { 59 | $uri = "http://127.0.0.1:57412/config/v1/project/_datalogger/log_groups/$($param1.Log_Group)/column_mappings/" 60 | $obj = Invoke-RestMethod -Uri $uri -Method 'Get' -Headers (Get-Header) 61 | Return ($obj) 62 | } 63 | 64 | Function Get-ProjectID { 65 | $uri = 'http://127.0.0.1:57412/config/v1/project/' 66 | $obj = Invoke-RestMethod -Uri $uri -Method 'Get' -Headers (Get-Header) 67 | $objProjID = $obj.'PROJECT_ID' 68 | Write-Output ($objProjID) 69 | } 70 | 71 | $logGroup = Get-LogGroup 72 | $columnMappings = Get-ColumnMappings 73 | $returnedColumnMappings = Get-KepColumnMappingDefaults ($logGroup) 74 | 75 | foreach ($x in $columnMappings) { 76 | foreach ($n in $returnedColumnMappings) { 77 | if ($n.'datalogger.TABLE_ALIAS_LOG_ITEM_ID' -eq $x.'LOG_ITEM_ID') { 78 | $n.'datalogger.TABLE_ALIAS_DATABASE_FIELD_NAME_VALUE' = $x.'Value_DBColumn' 79 | $n.'datalogger.TABLE_ALIAS_SQL_DATA_TYPE_VALUE' = [int]$x.'Value_Datatype' 80 | $n.'datalogger.TABLE_ALIAS_SQL_LENGTH_VALUE' = [int]$x.'Value_Length' 81 | $n."datalogger.TABLE_ALIAS_DATABASE_FIELD_NAME_NUMERIC" = $x.'NumericID_DBColumn' 82 | $n."datalogger.TABLE_ALIAS_SQL_DATA_TYPE_NUMERIC" = [int]$x.'NumericID_Datatype' 83 | $n."datalogger.TABLE_ALIAS_SQL_LENGTH_NUMERIC" = [int]$x.'NumericID_Length' 84 | $n."datalogger.TABLE_ALIAS_DATABASE_FIELD_NAME_QUALITY" = $x.'Quality_DBColumn' 85 | $n."datalogger.TABLE_ALIAS_SQL_DATA_TYPE_QUALITY" = [int]$x.'Quality_Datatype' 86 | $n."datalogger.TABLE_ALIAS_SQL_LENGTH_QUALITY" = [int]$x.'Quality_Length' 87 | $n."datalogger.TABLE_ALIAS_DATABASE_FIELD_NAME_TIMESTAMP" = $x.'Timestamp_DBColumn' 88 | $n."datalogger.TABLE_ALIAS_SQL_DATA_TYPE_TIMESTAMP" = [int]$x.'Timestamp_Datatype' 89 | $n."datalogger.TABLE_ALIAS_SQL_LENGTH_TIMESTAMP" = [int]$x.'Timestamp_Length' 90 | $n."datalogger.TABLE_ALIAS_DATABASE_FIELD_NAME_BATCHID" = $x.'BatchID_DBColumn' 91 | $n."datalogger.TABLE_ALIAS_SQL_DATA_TYPE_BATCHID" = [int]$x.'BatchID_Datatype' 92 | $n."datalogger.TABLE_ALIAS_SQL_LENGTH_BATCHID" = [int]$x.'BatchID_Length' 93 | $n.PROJECT_ID = Get-ProjectID 94 | $column = $n.'common.ALLTYPES_NAME' 95 | $uriColumnNumber = "http://127.0.0.1:57412/config/v1/project/_datalogger/log_groups/$($logGroup.Log_Group)/column_mappings/$($column)" 96 | $body = ConvertTo-Json $n 97 | Invoke-RestMethod -Uri $uriColumnNumber -Method 'PUT' -Headers (Get-Header) -Body $body 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Datalogger/Powershell/Use CSV to Configure Column Mappings/csvs/auth.csv: -------------------------------------------------------------------------------- 1 | UserName,Password 2 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Use CSV to Configure Column Mappings/csvs/columnMappings.csv: -------------------------------------------------------------------------------- 1 | Log_Item_ID,Value_DBColumn,Value_Datatype,Value_Length,Timestamp_DBColumn,Timestamp_Datatype,Timestamp_Length,Quality_DBColumn,Quality_Datatype,Quality_Length,NumericID_DBColumn,NumericID_Datatype,NumericID_Length,BatchID_Column,BatchID_Datatype,BatchID_Length 2 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Use CSV to Configure Column Mappings/csvs/logGroup.csv: -------------------------------------------------------------------------------- 1 | Log_Group 2 | -------------------------------------------------------------------------------- /Datalogger/Powershell/Use CSV to Configure Column Mappings/readme.md: -------------------------------------------------------------------------------- 1 | # Use a CSV file to configure wide format column mappings 2 | 3 | ## Purpose: 4 | - For a given Log Group, automatically configure tag-to-column mappings using a source CSV file 5 | 6 | ## To use: 7 | - Add desired tags to Log Group using GUI or API 8 | - Update [csvs/logGroup.csv](csvs/logGroup.csv) with target Log Group 9 | - Update [csvs/columnMappings.csv](csvs/columnMappings.csv) with desired tag mapping scheme, data types and lengths 10 | - Update [csvs/auth.csv](csvs/auth.csv)with Kepware Config API username and password 11 | 12 | ## Notes: 13 | - To quickly and easily source full tag paths (chan.dev..name) of log items with column mappings desired for custom configuration, run the helper script [exportLogItemsToCsv.ps1]() and copy the "datalogger.LOG_ITEM_ID" column from the exported CSV into the "Log_Item_ID" column of the columnMappings.csv file. 14 | - SQL Data Type Enumerations and Lengths 15 | 16 | | **Data Type** | **Enumeration** | **Length** | 17 | | :----------: | :----------: | :----------: | 18 | | Char | 1 | 64 | 19 | | Integer | 7 | 4 | 20 | | Real | 4 | 4 | 21 | | Smallint | 5 | 2 | 22 | | Timestamp | 11 | 16 | 23 | 24 | ## Requirements: 25 | - Database table must be pre-created and Log Group must be configured to Log to Existing Table 26 | - Wide Format must be used 27 | - If not already enabled, enable Kepware Server Configuration API service (consult Kepware Server documentation for details) 28 | - As necessary, edit the script's configuration to target correct Kepware Server IP address and Config API service port 29 | -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/Readme.md: -------------------------------------------------------------------------------- 1 | # Deploy and Map IoT Agents to devices with Azure IoT Hub 2 | 3 | This script automatically creates IoT devices within Azure IoT Hub and deploys corresponding MQTT agents to Kepware with appropriate connection settings. 4 | 5 | ## Dependencies 6 | 7 | - [Azure CLI 2.x.x](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) 8 | - [Azure CLI IoT Extension](https://github.com/Azure/azure-iot-cli-extension) (use the CLI to run 'az extension add --name azure-iot') 9 | - [Python 3.7 or above](https://www.python.org/downloads/) 10 | - Python [Requests](https://pypi.org/project/requests/) library 11 | 12 | ## Notes 13 | 14 | - Returns True is all operations succeed 15 | - Returns False if any operation fails 16 | - Assignment of user parameters is conducted through [setup.json](setup.json) 17 | - IoT device and MQTT agent quantity along with MQTT agent definition of custom, non-required properties is controlled through MQTT agent object file 18 | 19 | Review [createNewAsset.py](createNewAsset.py) for procedural details 20 | -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/createNewAsset.py: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # ------------------------------------------------------------------------- 3 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 4 | # See License.txt in the project root for 5 | # license information. 6 | # -------------------------------------------------------------------------- 7 | # Name: 8 | # Create_new_asset 9 | # Parameters: 10 | # Setup parameters defined in setup.json: 11 | # Sas key length in seconds 12 | # Path for kepware mqtt json snippet 13 | # Config api username 14 | # Config api password 15 | # Iot hub name 16 | # Iot device name 17 | # Iot gateway agent parameters defined in mqtt json snippet 18 | # One or an array of JSON objects of iot gateway mqtt agent in kepware server v6.7 with default settings 19 | # 20 | # Returns: 21 | # True - if both creating both agent and device are successful 22 | # False - if creating with agent or device fails 23 | # 24 | # Procedure: 25 | # Check if Azure user is logged in 26 | # Load setup parameters from file (or uncomment code for user input) 27 | # Handle file I/O errors 28 | # Check for required keys 29 | # Check to make sure certain required keys aren't empty 30 | # Load MQTT agent JSON object from file 31 | # Handle I/O errors 32 | # Check for required keys 33 | # !! To add: check to make sure certain required keys aren't empty 34 | # For each agent in JSON array 35 | # Create iot hub device 36 | # Generate SAS key 37 | # Modify JSON with required fields based on name and SAS key 38 | # Delete iot devices if device creation or sas key generation error 39 | # Post modified JSON object to kepware and handle errors 40 | # Delete devices from iot hub if HTTP post generates server response error 41 | # 42 | # *****************************************************************************/ 43 | 44 | def create_new_asset(): 45 | import json 46 | import requests 47 | import subprocess 48 | 49 | # make variables 50 | checkLoginCmd = "az ad signed-in-user show" 51 | azureIotHubName = "myCloudHub1" 52 | azureMqttClientUrl = "ssl://{}.azure-devices.net:8883".format(azureIotHubName) 53 | createdDeviceCount = 0 54 | 55 | # check if user is logged in, alert and end if not 56 | try: 57 | print ("\nChecking Azure account login status") 58 | returned_value = subprocess.check_output(checkLoginCmd, shell=True) 59 | print ("-- Login status OK") 60 | except subprocess.CalledProcessError as e: 61 | print ("-- No user logged in - Please run 'az login' using Azure CLI\n") 62 | print(e.output.decode("utf-8")) 63 | return False 64 | 65 | # grab config api U/P, device name, iot hub name, JSON file path from file 66 | setupFile = 'setup.json' 67 | try: 68 | print ("Loading 'setup.json' from local directory") 69 | with open (setupFile) as j: 70 | setupData = json.load (j) 71 | try: 72 | try: 73 | azureIotHubName = setupData['hubName'] 74 | deviceName = setupData['name'] 75 | configUsername = setupData.get('configApiUsername') 76 | configPassword = setupData['configApiPassword'] 77 | sasKeyLength = setupData['sasLen'] 78 | readFile = setupData['agentFile'] 79 | if azureIotHubName == '' or deviceName == '' or configUsername == '' or sasKeyLength == '' or readFile == '': 80 | print ("-- Load setup failed - missing parameters from setup file") 81 | return False 82 | except Exception as e: 83 | print ("-- Load setup failed - '{}'".format (e)) 84 | return False 85 | except KeyError as e: 86 | print ("-- Load setup failed - '{}'".format (e)) 87 | return False 88 | except Exception as e: 89 | print ("-- Load setup failed - '{}'".format (e)) 90 | return False 91 | 92 | # ******* User input for setup parameters *********************************************************************** 93 | """ 94 | # grab username from user 95 | i = 0 96 | while i == 0: 97 | configUsername = str(input ("Enter username for config API: ") or "administrator") 98 | if re.search (r'[\s]', configUsername): 99 | print ("No spaces please") 100 | else: 101 | print ("-- Accepted") 102 | i += 1 103 | 104 | #grab password from user 105 | i = 0 106 | while i == 0: 107 | configPassword = str(input("Enter password for config API (press enter for no password): ") or "") 108 | if re.search (r'[\s]', configPassword): 109 | print ("No spaces please") 110 | else: 111 | print ("-- Accepted") 112 | i += 1 113 | 114 | # ask user for iot hub name and base name for iot hub devices and mqtt agents 115 | i = 0 116 | while i == 0: 117 | azureIotHubName = str(input("Enter Azure IoT Hub name: ") or "myCloudHub1") 118 | if re.search (r'[\s]', azureIotHubName): 119 | print("No spaces please") 120 | else: 121 | print("-- Accepted") 122 | i += 1 123 | i = 0 124 | while i == 0: 125 | deviceName = str(input("Enter a base name (myDevice becomes myDevice1) for Azure IoT Hub devices and IoT Gateway MQTT agents:") or "myDevice") 126 | if re.search (r'[\s]', deviceName): 127 | print ("-- No spaces please") 128 | else: 129 | print ("-- Accepted") 130 | i += 1 131 | 132 | # grab file name 133 | readFile = str (input ("Enter full file path of JSON MQTT agents snippet including file name: ") or "kepware_object_snippets/arrayOfMqttAgents.json") 134 | print ("-- Accepted") 135 | """ 136 | # ****************************************************************************** 137 | 138 | # read in kepware iot gateway mqtt json template 139 | try: 140 | print ("Loading JSON snippet at '{}'".format (readFile)) 141 | with open (readFile) as j: 142 | jsonData = json.load(j) 143 | 144 | except IOError as e: 145 | print ("File load failed - '{}'".format(e)) 146 | return False; 147 | 148 | # check if keys are present in file 149 | agentNameKey = 'common.ALLTYPES_NAME' 150 | agentTypeKey = 'iot_gateway.AGENTTYPES_TYPE' 151 | agentEnabledKey = 'iot_gateway.AGENTTYPES_ENABLED' 152 | agentUrlKey = 'iot_gateway.MQTT_CLIENT_URL' 153 | agentTopicKey = 'iot_gateway.MQTT_CLIENT_TOPIC' 154 | agentQosKey = 'iot_gateway.MQTT_CLIENT_QOS' 155 | agentPubRateKey = 'iot_gateway.AGENTTYPES_RATE_MS' 156 | agentPubFormat = 'iot_gateway.AGENTTYPES_PUBLISH_FORMAT' 157 | agentMaxEvents = 'iot_gateway.AGENTTYPES_MAX_EVENTS' 158 | agentTimeoutKey = 'iot_gateway.AGENTTYPES_TIMEOUT_S' 159 | agentFormatKey = 'iot_gateway.AGENTTYPES_MESSAGE_FORMAT' 160 | agentStanTempKey = 'iot_gateway.AGENTTYPES_STANDARD_TEMPLATE' 161 | agentExpansionKey = 'iot_gateway.AGENTTYPES_EXPANSION_OF_VALUES' 162 | agentAdvTempKey = 'iot_gateway.AGENTTYPES_ADVANCED_TEMPLATE' 163 | agentClientIdKey = 'iot_gateway.MQTT_CLIENT_CLIENT_ID' 164 | agentUserNameKey = 'iot_gateway.MQTT_CLIENT_USERNAME' 165 | agentPasswordKey = 'iot_gateway.MQTT_CLIENT_PASSWORD' 166 | agentTlsKey = 'iot_gateway.MQTT_TLS_VERSION' 167 | agentCertificateKey = 'iot_gateway.MQTT_CLIENT_CERTIFICATE' 168 | agentWillKey = 'iot_gateway.MQTT_CLIENT_ENABLE_LAST_WILL' 169 | agentWillMessKey = 'iot_gateway.MQTT_CLIENT_LAST_WILL_MESSAGE' 170 | agentWriteEnableKey = 'iot_gateway.MQTT_CLIENT_ENABLE_WRITE_TOPIC' 171 | agentWriteTopicKey = 'iot_gateway.MQTT_CLIENT_WRITE_TOPIC' 172 | try: 173 | for r in jsonData: 174 | x = r[agentNameKey] 175 | x = r[agentTypeKey] 176 | x = r[agentEnabledKey] 177 | x = r[agentEnabledKey] 178 | x = r[agentUrlKey] 179 | x = r[agentTopicKey] 180 | x = r[agentQosKey] 181 | x = r[agentPubRateKey] 182 | x = r[agentPubFormat] 183 | x = r[agentMaxEvents] 184 | x = r[agentTimeoutKey] 185 | x = r[agentFormatKey] 186 | x = r[agentStanTempKey] 187 | x = r[agentExpansionKey] 188 | x = r[agentAdvTempKey] 189 | x = r[agentClientIdKey] 190 | x = r[agentUserNameKey] 191 | x = r[agentPasswordKey] 192 | x = r[agentTlsKey] 193 | x = r[agentCertificateKey] 194 | x = r[agentCertificateKey] 195 | x = r[agentWillKey] 196 | x = r[agentWillMessKey] 197 | x = r[agentWriteEnableKey] 198 | x = r[agentWriteTopicKey] 199 | except KeyError: 200 | print("-- Load failed, missing keys") 201 | return False 202 | print("-- Load succeeded") 203 | 204 | # create all iot devices using information from setup file 205 | try: 206 | i = 0 207 | if isinstance (jsonData, dict): 208 | nameNum = i + 1 209 | azureDeviceName = deviceName + "{}".format (nameNum) 210 | print ("Creating Azure IoT device with name '{}' on hub '{}'".format (azureDeviceName, azureIotHubName)) 211 | createDeviceCmd = "az iot hub device-identity create -n {} -d {}".format (azureIotHubName, azureDeviceName) 212 | returned_value = subprocess.check_output (createDeviceCmd, shell=True) 213 | print ("-- '{}' successfully created".format (azureDeviceName)) 214 | createdDeviceCount += 1 215 | else: 216 | while i < len (jsonData): 217 | nameNum = i + 1 218 | azureDeviceName = deviceName + "{}".format (nameNum) 219 | print("Creating Azure IoT device with name '{}' on hub '{}'".format(azureDeviceName, azureIotHubName)) 220 | createDeviceCmd = "az iot hub device-identity create -n {} -d {}".format(azureIotHubName, azureDeviceName) 221 | returned_value = subprocess.check_output(createDeviceCmd, shell=True) 222 | print ("-- '{}' successfully created".format (azureDeviceName)) 223 | azureDeviceName = deviceName 224 | i += 1 225 | createdDeviceCount += 1 226 | except subprocess.CalledProcessError as e: 227 | print(e.output.decode("utf-8")) 228 | if createdDeviceCount == 0: 229 | return False 230 | else: 231 | try: 232 | i = 0 233 | while i < createdDeviceCount: 234 | nameNum = i + 1 235 | azureDeviceName = deviceName + "{}".format (nameNum) 236 | print ("Deleting {} from {}".format (azureDeviceName, azureIotHubName)) 237 | deleteDeviceCmd = "az iot hub device-identity delete --hub-name {} --device-id {}".format (azureIotHubName, azureDeviceName) 238 | returned_value = subprocess.check_output (deleteDeviceCmd, shell=True) 239 | print ("-- {} successfully deleted".format (azureDeviceName)) 240 | i += 1 241 | return False 242 | except subprocess.CalledProcessError as e: 243 | print("Error deleting device: " + (e.output.decode ("utf-8"))) 244 | return False 245 | 246 | # generate SAS token for devices and update JSON data object with SAS token, device name and IoT Hub name 247 | i = 0 248 | if createdDeviceCount == 1: 249 | nameNum = i + 1 250 | azureDeviceName = deviceName + "{}".format (nameNum) 251 | print ("Generating SAS token for {}".format (azureDeviceName)) 252 | createDeviceSasCmd = "az iot hub generate-sas-token --hub-name {} --device-id {} --du {}".format ( 253 | azureIotHubName, azureDeviceName, sasKeyLength) 254 | sasToken = subprocess.check_output (createDeviceSasCmd, shell=True) 255 | utfToken = sasToken.decode ("utf-8") 256 | Token = utfToken.split ('}', 1)[0].rstrip () + '\n' + "}" 257 | jsonToken = json.loads (Token) 258 | print ("-- SAS Token:\n\n{}\n".format (jsonToken)) 259 | azureTopicName = "devices/{}/messages/events/".format (azureDeviceName) 260 | try: 261 | jsonData[i]['iot_gateway.MQTT_CLIENT_URL'] = azureMqttClientUrl 262 | jsonData[i]['common.ALLTYPES_NAME'] = azureDeviceName 263 | jsonData[i]['iot_gateway.MQTT_CLIENT_CLIENT_ID'] = azureDeviceName 264 | jsonData[i]['iot_gateway.MQTT_CLIENT_TOPIC'] = azureTopicName 265 | jsonData[i]['iot_gateway.MQTT_CLIENT_USERNAME'] = "{}.azure-devices.net/{}".format (azureIotHubName, 266 | azureDeviceName) 267 | jsonData[i]['iot_gateway.MQTT_CLIENT_PASSWORD'] = jsonToken['sas'] 268 | except KeyError: 269 | print ('-- Error editing JSON object - check keys\n') 270 | else: 271 | while i < createdDeviceCount: 272 | generatedSas = 0 273 | try: 274 | nameNum = i + 1 275 | azureDeviceName = deviceName + "{}".format (nameNum) 276 | print ("Generating SAS token for {}".format (azureDeviceName)) 277 | createDeviceSasCmd = "az iot hub generate-sas-token --hub-name {} --device-id {} --du {}".format(azureIotHubName, azureDeviceName, sasKeyLength) 278 | sasToken = subprocess.check_output(createDeviceSasCmd, shell=True) 279 | utfToken = sasToken.decode("utf-8") 280 | Token = utfToken.split ('}', 1)[0].rstrip() + '\n' + "}" 281 | jsonToken = json.loads(Token) 282 | print ("-- SAS Token:\n\n{}\n".format (jsonToken)) 283 | generatedSas += 1 284 | azureTopicName = "devices/{}/messages/events/".format (azureDeviceName) 285 | try: 286 | jsonData[i]['iot_gateway.MQTT_CLIENT_URL'] = azureMqttClientUrl 287 | jsonData[i]['common.ALLTYPES_NAME'] = azureDeviceName 288 | jsonData[i]['iot_gateway.MQTT_CLIENT_CLIENT_ID'] = azureDeviceName 289 | jsonData[i]['iot_gateway.MQTT_CLIENT_TOPIC'] = azureTopicName 290 | jsonData[i]['iot_gateway.MQTT_CLIENT_USERNAME'] = "{}.azure-devices.net/{}".format (azureIotHubName, azureDeviceName) 291 | jsonData[i]['iot_gateway.MQTT_CLIENT_PASSWORD'] = jsonToken['sas'] 292 | except KeyError: 293 | print ('-- Error editing JSON object - check keys\n') 294 | try: 295 | while i <= generatedSas and i < createdDeviceCount: 296 | nameNum = i + 1 297 | azureDeviceName = deviceName + "{}".format (nameNum) 298 | print ("Deleting {} from {}".format (azureDeviceName, azureIotHubName)) 299 | deleteDeviceCmd = "az iot hub device-identity delete --hub-name {} --device-id {}".format ( 300 | azureIotHubName, azureDeviceName) 301 | returned_value = subprocess.check_output (deleteDeviceCmd, shell=True) 302 | print ("-- {} successfully deleted".format (azureDeviceName)) 303 | azureDeviceName = deviceName 304 | i += 1 305 | return False 306 | except subprocess.CalledProcessError as e: 307 | print (e.output.decode ("utf-8")) 308 | return False 309 | i += 1 310 | azureDeviceName = deviceName 311 | except subprocess.CalledProcessError as e: 312 | print (e.output.decode ("utf-8")) 313 | try: 314 | while i <= generatedSas and i < createdDeviceCount: 315 | nameNum = i + 1 316 | azureDeviceName = deviceName + "{}".format (nameNum) 317 | print ("Deleting {} from {}".format (azureDeviceName, azureIotHubName)) 318 | deleteDeviceCmd = "az iot hub device-identity delete --hub-name {} --device-id {}".format (azureIotHubName, azureDeviceName) 319 | returned_value = subprocess.check_output (deleteDeviceCmd, shell=True) 320 | print ("-- {} successfully deleted".format (azureDeviceName)) 321 | azureDeviceName = deviceName 322 | i += 1 323 | return False 324 | except subprocess.CalledProcessError as e: 325 | print (e.output.decode ("utf-8")) 326 | return False 327 | 328 | # post mqtt agent to kepware 329 | try: 330 | iotGatewayEndpoint = 'http://{}:{}@127.0.0.1:57412/config/v1/project/_iot_gateway/mqtt_clients'''.format (configUsername, configPassword) 331 | print ("Creating MQTT Client Agents at URL '{}'".format(iotGatewayEndpoint)) 332 | print ("-- Posting JSON".format (azureDeviceName)) 333 | r = requests.post (url=iotGatewayEndpoint, json=jsonData) 334 | r.raise_for_status () 335 | print ("-- Agents created") 336 | print ("\nExiting\n".format(azureDeviceName)) 337 | return True 338 | except requests.exceptions.HTTPError as err: 339 | response = r.content 340 | print("-- Received error response from REST Config API: '{}".format(err, response)) 341 | print("Response content: {}".format(response)) 342 | try: 343 | i = 0 344 | while i < createdDeviceCount: 345 | nameNum = i + 1 346 | azureDeviceName = deviceName + "{}".format (nameNum) 347 | print ("Deleting {} from {}".format (azureDeviceName, azureIotHubName)) 348 | deleteDeviceCmd = "az iot hub device-identity delete --hub-name {} --device-id {}".format ( 349 | azureIotHubName, azureDeviceName) 350 | returned_value = subprocess.check_output (deleteDeviceCmd, shell=True) 351 | print ("-- {} successfully deleted".format (azureDeviceName)) 352 | azureDeviceName = deviceName 353 | i += 1 354 | return False 355 | except subprocess.CalledProcessError as e: 356 | print ("Error deleting device: " + (e.output.decode ("utf-8"))) 357 | return False 358 | 359 | 360 | """ 361 | if isinstance(jsonData, dict): 362 | print("---- {}".format(response)) 363 | try: 364 | i = 0 365 | if isinstance (jsonData, dict): 366 | nameNum = i + 1 367 | azureDeviceName = deviceName + "{}".format (nameNum) 368 | deleteDeviceCmd = "az iot hub device-identity delete --hub-name {} --device-id {}".format ( 369 | azureIotHubName, azureDeviceName) 370 | print ("Deleting '{}' from IoT Hub".format (azureDeviceName)) 371 | returned_value = subprocess.check_output (deleteDeviceCmd, shell=True) 372 | print ("-- '{}' successfully deleted from IoT Hub".format (azureDeviceName)) 373 | azureDeviceName = deviceName 374 | else: 375 | while i < len (jsonData): 376 | nameNum = i + 1 377 | azureDeviceName = deviceName + "{}".format (nameNum) 378 | deleteDeviceCmd = "az iot hub device-identity delete --hub-name {} --device-id {}".format ( 379 | azureIotHubName, azureDeviceName) 380 | print ("Deleting '{}' from IoT Hub".format (azureDeviceName)) 381 | returned_value = subprocess.check_output (deleteDeviceCmd, shell=True) 382 | print ("-- '{}' successfully deleted from IoT Hub".format (azureDeviceName)) 383 | azureDeviceName = deviceName 384 | i += 1 385 | return False 386 | except subprocess.CalledProcessError as e: 387 | print (e.output.decode ("utf-8")) 388 | return False 389 | else: 390 | for m in response: 391 | resp = m['message'] 392 | print ("---- {}".format (resp)) 393 | try: 394 | i = 0 395 | while i < len(jsonData): 396 | nameNum = i + 1 397 | azureDeviceName = deviceName + "{}".format (nameNum) 398 | deleteDeviceCmd = "az iot hub device-identity delete --hub-name {} --device-id {}".format (azureIotHubName, azureDeviceName) 399 | print("Deleting '{}' from IoT Hub".format(azureDeviceName)) 400 | returned_value = subprocess.check_output (deleteDeviceCmd, shell=True) 401 | print("-- '{}' successfully deleted from IoT Hub".format(azureDeviceName)) 402 | azureDeviceName = deviceName 403 | i += 1 404 | return False 405 | except subprocess.CalledProcessError as e: 406 | print (e.output.decode ("utf-8")) 407 | return False 408 | """ 409 | 410 | returnCode = create_new_asset() 411 | print("Return Code: {}".format(returnCode)) -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/kepware_object_snippets/agents.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "common.ALLTYPES_NAME": "mqttAgent1", 3 | "iot_gateway.AGENTTYPES_TYPE": "MQTT Client", 4 | "iot_gateway.AGENTTYPES_ENABLED": true, 5 | "iot_gateway.MQTT_CLIENT_URL": "", 6 | "iot_gateway.MQTT_CLIENT_TOPIC": "", 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 | "iot_items": [ 27 | { 28 | "common.ALLTYPES_NAME": "System__TotalTagCount", 29 | "iot_gateway.IOT_ITEM_SERVER_TAG": "_System._TotalTagCount", 30 | "iot_gateway.IOT_ITEM_USE_SCAN_RATE": true, 31 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 1000, 32 | "iot_gateway.IOT_ITEM_SEND_EVERY_SCAN": false, 33 | "iot_gateway.IOT_ITEM_DEADBAND_PERCENT": 0, 34 | "iot_gateway.IOT_ITEM_ENABLED": true, 35 | "iot_gateway.IOT_ITEM_DATA_TYPE": 7 36 | } 37 | ] 38 | },{ 39 | "common.ALLTYPES_NAME": "mqttAgent1", 40 | "iot_gateway.AGENTTYPES_TYPE": "MQTT Client", 41 | "iot_gateway.AGENTTYPES_ENABLED": true, 42 | "iot_gateway.MQTT_CLIENT_URL": "", 43 | "iot_gateway.MQTT_CLIENT_TOPIC": "", 44 | "iot_gateway.MQTT_CLIENT_QOS": 1, 45 | "iot_gateway.AGENTTYPES_RATE_MS": 10000, 46 | "iot_gateway.AGENTTYPES_PUBLISH_FORMAT": 0, 47 | "iot_gateway.AGENTTYPES_MAX_EVENTS": 1000, 48 | "iot_gateway.AGENTTYPES_TIMEOUT_S": 5, 49 | "iot_gateway.AGENTTYPES_MESSAGE_FORMAT": 0, 50 | "iot_gateway.AGENTTYPES_STANDARD_TEMPLATE": "timestamp: |SERVERTIMESTAMP|\r\nvalues: |VALUES|\r\n", 51 | "iot_gateway.AGENTTYPES_EXPANSION_OF_VALUES": "id: |TAGNAME|\r\nv: |TAGVALUE|\r\nq: |TAGQUALITY|\r\nt: |TAGTIMESTAMP|\r\n", 52 | "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}", 53 | "iot_gateway.MQTT_CLIENT_CLIENT_ID": "", 54 | "iot_gateway.MQTT_CLIENT_USERNAME": "", 55 | "iot_gateway.MQTT_CLIENT_PASSWORD": "", 56 | "iot_gateway.MQTT_TLS_VERSION": 0, 57 | "iot_gateway.MQTT_CLIENT_CERTIFICATE": false, 58 | "iot_gateway.MQTT_CLIENT_ENABLE_LAST_WILL": false, 59 | "iot_gateway.MQTT_CLIENT_LAST_WILL_TOPIC": "", 60 | "iot_gateway.MQTT_CLIENT_LAST_WILL_MESSAGE": "", 61 | "iot_gateway.MQTT_CLIENT_ENABLE_WRITE_TOPIC": false, 62 | "iot_gateway.MQTT_CLIENT_WRITE_TOPIC": "iotgateway/write", 63 | "iot_items": [ 64 | { 65 | "common.ALLTYPES_NAME": "System__TotalTagCount", 66 | "iot_gateway.IOT_ITEM_SERVER_TAG": "_System._TotalTagCount", 67 | "iot_gateway.IOT_ITEM_USE_SCAN_RATE": true, 68 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 1000, 69 | "iot_gateway.IOT_ITEM_SEND_EVERY_SCAN": false, 70 | "iot_gateway.IOT_ITEM_DEADBAND_PERCENT": 0, 71 | "iot_gateway.IOT_ITEM_ENABLED": true, 72 | "iot_gateway.IOT_ITEM_DATA_TYPE": 7 73 | } 74 | ] 75 | }] -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/kepware_object_snippets/arrayOfMqttAgents.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "common.ALLTPYES_NAME": "mqttAgent1", 3 | "iot_gateway.AGENTTYPES_TYPE": "MQTT Client", 4 | "iot_gateway.AGENTTYPES_ENABLED": true, 5 | "iot_gateway.MQTT_CLIENT_URL": "", 6 | "iot_gateway.MQTT_CLIENT_TOPIC": "", 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 | "iot_items": [ 27 | { 28 | "common.ALLTYPES_NAME": "System__TotalTagCount", 29 | "iot_gateway.IOT_ITEM_SERVER_TAG": "_System._TotalTagCount", 30 | "iot_gateway.IOT_ITEM_USE_SCAN_RATE": true, 31 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 1000, 32 | "iot_gateway.IOT_ITEM_SEND_EVERY_SCAN": false, 33 | "iot_gateway.IOT_ITEM_DEADBAND_PERCENT": 0, 34 | "iot_gateway.IOT_ITEM_ENABLED": true, 35 | "iot_gateway.IOT_ITEM_DATA_TYPE": 7 36 | } 37 | ] 38 | },{ 39 | "common.ALLTYPES_NAME": "mqttAgent1", 40 | "iot_gateway.AGENTTYPES_TYPE": "MQTT Client", 41 | "iot_gateway.AGENTTYPES_ENABLED": true, 42 | "iot_gateway.MQTT_CLIENT_URL": "", 43 | "iot_gateway.MQTT_CLIENT_TOPIC": "", 44 | "iot_gateway.MQTT_CLIENT_QOS": 1, 45 | "iot_gateway.AGENTTYPES_RATE_MS": 10000, 46 | "iot_gateway.AGENTTYPES_PUBLISH_FORMAT": 0, 47 | "iot_gateway.AGENTTYPES_MAX_EVENTS": 1000, 48 | "iot_gateway.AGENTTYPES_TIMEOUT_S": 5, 49 | "iot_gateway.AGENTTYPES_MESSAGE_FORMAT": 0, 50 | "iot_gateway.AGENTTYPES_STANDARD_TEMPLATE": "timestamp: |SERVERTIMESTAMP|\r\nvalues: |VALUES|\r\n", 51 | "iot_gateway.AGENTTYPES_EXPANSION_OF_VALUES": "id: |TAGNAME|\r\nv: |TAGVALUE|\r\nq: |TAGQUALITY|\r\nt: |TAGTIMESTAMP|\r\n", 52 | "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}", 53 | "iot_gateway.MQTT_CLIENT_CLIENT_D": "", 54 | "iot_gateway.MQTT_CLIENT_USERNAME": "", 55 | "iot_gateway.MQTT_CLIENT_PASSWORD": "", 56 | "iot_gateway.MQTT_TLS_VERSION": 0, 57 | "iot_gateway.MQTT_CLIENT_CERTIFICATE": false, 58 | "iot_gateway.MQTT_CLIENT_ENABLE_LAST_WILL": false, 59 | "iot_gateway.MQTT_CLIENT_LAST_WILL_TOPIC": "", 60 | "iot_gateway.MQTT_CLIENT_LAST_WILL_MESSAGE": "", 61 | "iot_gateway.MQTT_CLIENT_ENABLE_WRITE_TOPIC": false, 62 | "iot_gateway.MQTT_CLIENT_WRITE_TOPIC": "iotgateway/write", 63 | "iot_items": [ 64 | { 65 | "common.ALLTYPES_NAME": "System__TotalTagCount", 66 | "iot_gateway.IOT_ITEM_SERVER_TAG": "_System._TotalTagCount", 67 | "iot_gateway.IOT_ITEM_USE_SCAN_RATE": true, 68 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 1000, 69 | "iot_gateway.IOT_ITEM_SEND_EVERY_SCAN": false, 70 | "iot_gateway.IOT_ITEM_DEADBAND_PERCENT": 0, 71 | "iot_gateway.IOT_ITEM_ENABLED": true, 72 | "iot_gateway.IOT_ITEM_DATA_TYPE": 7 73 | } 74 | ] 75 | },{ 76 | "common.ALLTYPES_NAME": "mqttAgent1", 77 | "iot_gateway.AGENTTYPES_TYPE": "MQTT Client", 78 | "iot_gateway.AGENTTYPES_ENABLED": true, 79 | "iot_gateway.MQTT_CLIENT_URL": "", 80 | "iot_gateway.MQTT_CLIENT_TOPIC": "", 81 | "iot_gateway.MQTT_CLIENT_QOS": 1, 82 | "iot_gateway.AGENTTYPES_RATE_MS": 10000, 83 | "iot_gateway.AGENTTYPES_PUBLISH_FORMAT": 0, 84 | "iot_gateway.AGENTTYPES_MAX_EVENTS": 1000, 85 | "iot_gateway.AGENTTYPES_TIMEOUT_S": 5, 86 | "iot_gateway.AGENTTYPES_MESSAGE_FORMAT": 0, 87 | "iot_gateway.AGENTTYPES_STANDARD_TEMPLATE": "timestamp: |SERVERTIMESTAMP|\r\nvalues: |VALUES|\r\n", 88 | "iot_gateway.AGENTTYPES_EXPANSION_OF_VALUES": "id: |TAGNAME|\r\nv: |TAGVALUE|\r\nq: |TAGQUALITY|\r\nt: |TAGTIMESTAMP|\r\n", 89 | "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}", 90 | "iot_gateway.MQTT_CLIENT_CLIENT_ID": "", 91 | "iot_gateway.MQTT_CLIENT_USERNAME": "", 92 | "iot_gateway.MQTT_CLIENT_PASSWORD": "", 93 | "iot_gateway.MQTT_TLS_VERSION": 0, 94 | "iot_gateway.MQTT_CLIENT_CERTIFICATE": false, 95 | "iot_gateway.MQTT_CLIENT_ENABLE_LAST_WILL": false, 96 | "iot_gateway.MQTT_CLIENT_LAST_WILL_TOPIC": "", 97 | "iot_gateway.MQTT_CLIENT_LAST_WILL_MESSAGE": "", 98 | "iot_gateway.MQTT_CLIENT_ENABLE_WRITE_TOPIC": false, 99 | "iot_gateway.MQTT_CLIENT_WRITE_TOPIC": "iotgateway/write", 100 | "iot_items": [ 101 | { 102 | "common.ALLTYPES_NAME": "System__TotalTagCount", 103 | "iot_gateway.IOT_ITEM_SERVER_TAG": "_System._TotalTagCount", 104 | "iot_gateway.IOT_ITEM_USE_SCAN_RATE": true, 105 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 1000, 106 | "iot_gateway.IOT_ITEM_SEND_EVERY_SCAN": false, 107 | "iot_gateway.IOT_ITEM_DEADBAND_PERCENT": 0, 108 | "iot_gateway.IOT_ITEM_ENABLED": true, 109 | "iot_gateway.IOT_ITEM_DATA_TYPE": 7 110 | } 111 | ] 112 | }] 113 | -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/kepware_object_snippets/templates/BACnet Channel and Device - Kepware JSON Snippet.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "bacnetDevices", 3 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "BACnet/IP", 4 | "servermain.CHANNEL_DIAGNOSTICS_CAPTURE": false, 5 | "servermain.CHANNEL_UNIQUE_ID": 2141399821, 6 | "servermain.CHANNEL_ETHERNET_COMMUNICATIONS_NETWORK_ADAPTER_STRING": "", 7 | "servermain.CHANNEL_WRITE_OPTIMIZATIONS_METHOD": 2, 8 | "servermain.CHANNEL_WRITE_OPTIMIZATIONS_DUTY_CYCLE": 10, 9 | "servermain.CHANNEL_NON_NORMALIZED_FLOATING_POINT_HANDLING": 1, 10 | "bacnet.CHANNEL_ALLOW_COV_NOTIFICATIONS": 0, 11 | "bacnet.CHANNEL_UDP_PORT": 47808, 12 | "bacnet.CHANNEL_NETWORK_NUMBER": 1, 13 | "bacnet.CHANNEL_DEVICE_INSTANCE": 0, 14 | "bacnet.CHANNEL_FOREIGN_DEVICE": 0, 15 | "bacnet.CHANNEL_IP_ADDRESS_OF_REMOTE_BBMD": "0.0.0.0", 16 | "bacnet.CHANNEL_REGISTRATION_TIME_TO_LIVE": 60, 17 | "devices": [ 18 | { 19 | "common.ALLTYPES_NAME": "d1", 20 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "BACnet/IP", 21 | "servermain.DEVICE_MODEL": 0, 22 | "servermain.DEVICE_UNIQUE_ID": 3938774601, 23 | "servermain.DEVICE_ID_FORMAT": 0, 24 | "servermain.DEVICE_ID_STRING": "1.100", 25 | "servermain.DEVICE_ID_HEXADECIMAL": 0, 26 | "servermain.DEVICE_ID_DECIMAL": 0, 27 | "servermain.DEVICE_ID_OCTAL": 0, 28 | "servermain.DEVICE_DATA_COLLECTION": true, 29 | "servermain.DEVICE_SIMULATED": false, 30 | "servermain.DEVICE_SCAN_MODE": 0, 31 | "servermain.DEVICE_SCAN_MODE_RATE_MS": 1000, 32 | "servermain.DEVICE_SCAN_MODE_PROVIDE_INITIAL_UPDATES_FROM_CACHE": false, 33 | "servermain.DEVICE_CONNECTION_TIMEOUT_SECONDS": 3, 34 | "servermain.DEVICE_REQUEST_TIMEOUT_MILLISECONDS": 1000, 35 | "servermain.DEVICE_RETRY_ATTEMPTS": 3, 36 | "servermain.DEVICE_AUTO_DEMOTION_ENABLE_ON_COMMUNICATIONS_FAILURES": false, 37 | "servermain.DEVICE_AUTO_DEMOTION_DEMOTE_AFTER_SUCCESSIVE_TIMEOUTS": 3, 38 | "servermain.DEVICE_AUTO_DEMOTION_PERIOD_MS": 10000, 39 | "servermain.DEVICE_AUTO_DEMOTION_DISCARD_WRITES": false, 40 | "servermain.DEVICE_TAG_GENERATION_ON_STARTUP": 0, 41 | "servermain.DEVICE_TAG_GENERATION_DUPLICATE_HANDLING": 0, 42 | "servermain.DEVICE_TAG_GENERATION_GROUP": "", 43 | "servermain.DEVICE_TAG_GENERATION_ALLOW_SUB_GROUPS": true, 44 | "bacnet.DEVICE_MAXIMUM_SEGMENTS": 0, 45 | "bacnet.DEVICE_MAXIMUM_SEGMENT_WINDOW_SIZE": 1, 46 | "bacnet.DEVICE_MAXIMUM_APDU_LENGTH": 5, 47 | "bacnet.DEVICE_MAXIMUM_ITEMS_PER_REQUEST": 16, 48 | "bacnet.DEVICE_COMMAND_PRIORITY": 8, 49 | "bacnet.DEVICE_COV_MODE": 0, 50 | "bacnet.DEVICE_USE_SPID_OF_0": false, 51 | "bacnet.DEVICE_CANCEL_COV_SUBSCRIPTIONS": true, 52 | "bacnet.DEVICE_AWAIT_COV_CANCELLATION_ACKS": false, 53 | "bacnet.DEVICE_COV_RESUBSCRIPTION_INTERVAL": 3600, 54 | "bacnet.DEVICE_EVENT_NOTIFICATIONS_ENABLE": false, 55 | "bacnet.DEVICE_EVENT_NOTIFICATIONS_LIST": "", 56 | "bacnet.DEVICE_IMPORT_METHOD": 0, 57 | "bacnet.DEVICE_IMPORT_FILE": "*.csv", 58 | "bacnet.DEVICE_EXCLUDE_OPTIONAL_PROPERTIES": false, 59 | "bacnet.DEVICE_CREATE_READ_WRITE_TAGS": false, 60 | "bacnet.DEVICE_USE_OBJECT_NAMES": false, 61 | "bacnet.DEVICE_TAG_IMPORT_ACCUMULATOR": false, 62 | "bacnet.DEVICE_TAG_IMPORT_ANALOG_INPUTS": true, 63 | "bacnet.DEVICE_TAG_IMPORT_ANALOG_OUTPUTS": true, 64 | "bacnet.DEVICE_TAG_IMPORT_ANALOG_VALUE": false, 65 | "bacnet.DEVICE_TAG_IMPORT_AVERAGING": false, 66 | "bacnet.DEVICE_TAG_IMPORT_BINARY_INPUTS": true, 67 | "bacnet.DEVICE_TAG_IMPORT_BINARY_OUTPUTS": true, 68 | "bacnet.DEVICE_TAG_IMPORT_BINARY_VALUES": false, 69 | "bacnet.DEVICE_TAG_IMPORT_CALENDAR": false, 70 | "bacnet.DEVICE_TAG_IMPORT_COMMAND": false, 71 | "bacnet.DEVICE_TAG_IMPORT_DEVICE": false, 72 | "bacnet.DEVICE_TAG_IMPORT_EVENT_ENROLLMENT": false, 73 | "bacnet.DEVICE_TAG_IMPORT_FILE": false, 74 | "bacnet.DEVICE_TAG_IMPORT_GROUP": false, 75 | "bacnet.DEVICE_TAG_IMPORT_LIFE_SAFETY_POINT": false, 76 | "bacnet.DEVICE_TAG_IMPORT_LIFE_SAFETY_ZONE": false, 77 | "bacnet.DEVICE_TAG_IMPORT_LOOP": false, 78 | "bacnet.DEVICE_TAG_IMPORT_MULTISTATE_INPUT": false, 79 | "bacnet.DEVICE_TAG_IMPORT_MULTISTATE_OUTPUT": false, 80 | "bacnet.DEVICE_TAG_IMPORT_MULTISTATE_VALUE": false, 81 | "bacnet.DEVICE_TAG_IMPORT_NOTIFICATION_CLASS": false, 82 | "bacnet.DEVICE_TAG_IMPORT_PROGRAM": false, 83 | "bacnet.DEVICE_TAG_IMPORT_SCHEDULE": false, 84 | "bacnet.DEVICE_TAG_IMPORT_TREND_LOG": false, 85 | "bacnet.DEVICE_DISCOVERY_METHOD": 0, 86 | "bacnet.DEVICE_DISCOVERY_SCOPE": 0, 87 | "bacnet.DEVICE_AUTOMATIC_DISCOVERY_IP_ADDRESS": "255.255.255.255", 88 | "bacnet.DEVICE_MANUAL_DISCOVERY_IP_ADDRESS": "255.255.255.255", 89 | "bacnet.DEVICE_REMOTE_DATA_LINK_TECHNOLOGY": false, 90 | "bacnet.DEVICE_BACNET_MAC": "", 91 | "redundancy.DEVICE_SECONDARY_PATH": "", 92 | "redundancy.DEVICE_OPERATING_MODE": 0, 93 | "redundancy.DEVICE_MONITOR_ITEM": "", 94 | "redundancy.DEVICE_MONITOR_ITEM_POLL_INTERVAL": 300, 95 | "redundancy.DEVICE_FAVOR_PRIMARY": true, 96 | "redundancy.DEVICE_TRIGGER_ITEM": "", 97 | "redundancy.DEVICE_TRIGGER_ITEM_SCAN_RATE": 1000, 98 | "redundancy.DEVICE_TRIGGER_TYPE": 0, 99 | "redundancy.DEVICE_TRIGGER_OPERATOR": 0, 100 | "redundancy.DEVICE_TRIGGER_VALUE": "", 101 | "redundancy.DEVICE_TRIGGER_TIMEOUT": 10000 102 | } 103 | ] 104 | } -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/kepware_object_snippets/templates/BACnet Device and Tags - Kepware JSON Snippet.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "bacnetDevices", 3 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "BACnet/IP", 4 | "servermain.CHANNEL_DIAGNOSTICS_CAPTURE": false, 5 | "servermain.CHANNEL_UNIQUE_ID": 2141399821, 6 | "servermain.CHANNEL_ETHERNET_COMMUNICATIONS_NETWORK_ADAPTER_STRING": "", 7 | "servermain.CHANNEL_WRITE_OPTIMIZATIONS_METHOD": 2, 8 | "servermain.CHANNEL_WRITE_OPTIMIZATIONS_DUTY_CYCLE": 10, 9 | "servermain.CHANNEL_NON_NORMALIZED_FLOATING_POINT_HANDLING": 1, 10 | "bacnet.CHANNEL_ALLOW_COV_NOTIFICATIONS": 0, 11 | "bacnet.CHANNEL_UDP_PORT": 47810, 12 | "bacnet.CHANNEL_NETWORK_NUMBER": 1, 13 | "bacnet.CHANNEL_DEVICE_INSTANCE": 0, 14 | "bacnet.CHANNEL_FOREIGN_DEVICE": 0, 15 | "bacnet.CHANNEL_IP_ADDRESS_OF_REMOTE_BBMD": "0.0.0.0", 16 | "bacnet.CHANNEL_REGISTRATION_TIME_TO_LIVE": 60, 17 | "devices": [ 18 | { 19 | "common.ALLTYPES_NAME": "d1", 20 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "BACnet/IP", 21 | "servermain.DEVICE_MODEL": 0, 22 | "servermain.DEVICE_UNIQUE_ID": 3938774601, 23 | "servermain.DEVICE_ID_FORMAT": 0, 24 | "servermain.DEVICE_ID_STRING": "1.100", 25 | "servermain.DEVICE_ID_HEXADECIMAL": 0, 26 | "servermain.DEVICE_ID_DECIMAL": 0, 27 | "servermain.DEVICE_ID_OCTAL": 0, 28 | "servermain.DEVICE_DATA_COLLECTION": true, 29 | "servermain.DEVICE_SIMULATED": false, 30 | "servermain.DEVICE_SCAN_MODE": 0, 31 | "servermain.DEVICE_SCAN_MODE_RATE_MS": 1000, 32 | "servermain.DEVICE_SCAN_MODE_PROVIDE_INITIAL_UPDATES_FROM_CACHE": false, 33 | "servermain.DEVICE_CONNECTION_TIMEOUT_SECONDS": 3, 34 | "servermain.DEVICE_REQUEST_TIMEOUT_MILLISECONDS": 1000, 35 | "servermain.DEVICE_RETRY_ATTEMPTS": 3, 36 | "servermain.DEVICE_AUTO_DEMOTION_ENABLE_ON_COMMUNICATIONS_FAILURES": false, 37 | "servermain.DEVICE_AUTO_DEMOTION_DEMOTE_AFTER_SUCCESSIVE_TIMEOUTS": 3, 38 | "servermain.DEVICE_AUTO_DEMOTION_PERIOD_MS": 10000, 39 | "servermain.DEVICE_AUTO_DEMOTION_DISCARD_WRITES": false, 40 | "servermain.DEVICE_TAG_GENERATION_ON_STARTUP": 0, 41 | "servermain.DEVICE_TAG_GENERATION_DUPLICATE_HANDLING": 0, 42 | "servermain.DEVICE_TAG_GENERATION_GROUP": "", 43 | "servermain.DEVICE_TAG_GENERATION_ALLOW_SUB_GROUPS": true, 44 | "bacnet.DEVICE_MAXIMUM_SEGMENTS": 0, 45 | "bacnet.DEVICE_MAXIMUM_SEGMENT_WINDOW_SIZE": 1, 46 | "bacnet.DEVICE_MAXIMUM_APDU_LENGTH": 5, 47 | "bacnet.DEVICE_MAXIMUM_ITEMS_PER_REQUEST": 16, 48 | "bacnet.DEVICE_COMMAND_PRIORITY": 8, 49 | "bacnet.DEVICE_COV_MODE": 0, 50 | "bacnet.DEVICE_USE_SPID_OF_0": false, 51 | "bacnet.DEVICE_CANCEL_COV_SUBSCRIPTIONS": true, 52 | "bacnet.DEVICE_AWAIT_COV_CANCELLATION_ACKS": false, 53 | "bacnet.DEVICE_COV_RESUBSCRIPTION_INTERVAL": 3600, 54 | "bacnet.DEVICE_EVENT_NOTIFICATIONS_ENABLE": false, 55 | "bacnet.DEVICE_EVENT_NOTIFICATIONS_LIST": "", 56 | "bacnet.DEVICE_IMPORT_METHOD": 0, 57 | "bacnet.DEVICE_IMPORT_FILE": "*.csv", 58 | "bacnet.DEVICE_EXCLUDE_OPTIONAL_PROPERTIES": false, 59 | "bacnet.DEVICE_CREATE_READ_WRITE_TAGS": false, 60 | "bacnet.DEVICE_USE_OBJECT_NAMES": false, 61 | "bacnet.DEVICE_TAG_IMPORT_ACCUMULATOR": false, 62 | "bacnet.DEVICE_TAG_IMPORT_ANALOG_INPUTS": true, 63 | "bacnet.DEVICE_TAG_IMPORT_ANALOG_OUTPUTS": true, 64 | "bacnet.DEVICE_TAG_IMPORT_ANALOG_VALUE": false, 65 | "bacnet.DEVICE_TAG_IMPORT_AVERAGING": false, 66 | "bacnet.DEVICE_TAG_IMPORT_BINARY_INPUTS": true, 67 | "bacnet.DEVICE_TAG_IMPORT_BINARY_OUTPUTS": true, 68 | "bacnet.DEVICE_TAG_IMPORT_BINARY_VALUES": false, 69 | "bacnet.DEVICE_TAG_IMPORT_CALENDAR": false, 70 | "bacnet.DEVICE_TAG_IMPORT_COMMAND": false, 71 | "bacnet.DEVICE_TAG_IMPORT_DEVICE": false, 72 | "bacnet.DEVICE_TAG_IMPORT_EVENT_ENROLLMENT": false, 73 | "bacnet.DEVICE_TAG_IMPORT_FILE": false, 74 | "bacnet.DEVICE_TAG_IMPORT_GROUP": false, 75 | "bacnet.DEVICE_TAG_IMPORT_LIFE_SAFETY_POINT": false, 76 | "bacnet.DEVICE_TAG_IMPORT_LIFE_SAFETY_ZONE": false, 77 | "bacnet.DEVICE_TAG_IMPORT_LOOP": false, 78 | "bacnet.DEVICE_TAG_IMPORT_MULTISTATE_INPUT": false, 79 | "bacnet.DEVICE_TAG_IMPORT_MULTISTATE_OUTPUT": false, 80 | "bacnet.DEVICE_TAG_IMPORT_MULTISTATE_VALUE": false, 81 | "bacnet.DEVICE_TAG_IMPORT_NOTIFICATION_CLASS": false, 82 | "bacnet.DEVICE_TAG_IMPORT_PROGRAM": false, 83 | "bacnet.DEVICE_TAG_IMPORT_SCHEDULE": false, 84 | "bacnet.DEVICE_TAG_IMPORT_TREND_LOG": false, 85 | "bacnet.DEVICE_DISCOVERY_METHOD": 0, 86 | "bacnet.DEVICE_DISCOVERY_SCOPE": 0, 87 | "bacnet.DEVICE_AUTOMATIC_DISCOVERY_IP_ADDRESS": "255.255.255.255", 88 | "bacnet.DEVICE_MANUAL_DISCOVERY_IP_ADDRESS": "255.255.255.255", 89 | "bacnet.DEVICE_REMOTE_DATA_LINK_TECHNOLOGY": false, 90 | "bacnet.DEVICE_BACNET_MAC": "", 91 | "redundancy.DEVICE_SECONDARY_PATH": "", 92 | "redundancy.DEVICE_OPERATING_MODE": 0, 93 | "redundancy.DEVICE_MONITOR_ITEM": "", 94 | "redundancy.DEVICE_MONITOR_ITEM_POLL_INTERVAL": 300, 95 | "redundancy.DEVICE_FAVOR_PRIMARY": true, 96 | "redundancy.DEVICE_TRIGGER_ITEM": "", 97 | "redundancy.DEVICE_TRIGGER_ITEM_SCAN_RATE": 1000, 98 | "redundancy.DEVICE_TRIGGER_TYPE": 0, 99 | "redundancy.DEVICE_TRIGGER_OPERATOR": 0, 100 | "redundancy.DEVICE_TRIGGER_VALUE": "", 101 | "redundancy.DEVICE_TRIGGER_TIMEOUT": 10000, 102 | "tag_groups": [ 103 | { 104 | "common.ALLTYPES_NAME": "ai", 105 | "tags": [ 106 | { 107 | "common.ALLTYPES_NAME": "AI_0", 108 | "servermain.TAG_ADDRESS": "AnalogInput.0.PresentValue", 109 | "servermain.TAG_DATA_TYPE": 8, 110 | "servermain.TAG_READ_WRITE_ACCESS": 1, 111 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 112 | "servermain.TAG_SCALING_TYPE": 0 113 | }, 114 | { 115 | "common.ALLTYPES_NAME": "AI_1", 116 | "servermain.TAG_ADDRESS": "AnalogInput.1.PresentValue", 117 | "servermain.TAG_DATA_TYPE": 8, 118 | "servermain.TAG_READ_WRITE_ACCESS": 1, 119 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 120 | "servermain.TAG_SCALING_TYPE": 0 121 | }, 122 | { 123 | "common.ALLTYPES_NAME": "AI_2", 124 | "servermain.TAG_ADDRESS": "AnalogInput.2.PresentValue", 125 | "servermain.TAG_DATA_TYPE": 8, 126 | "servermain.TAG_READ_WRITE_ACCESS": 1, 127 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 128 | "servermain.TAG_SCALING_TYPE": 0 129 | }, 130 | { 131 | "common.ALLTYPES_NAME": "AI_3", 132 | "servermain.TAG_ADDRESS": "AnalogInput.3.PresentValue", 133 | "servermain.TAG_DATA_TYPE": 8, 134 | "servermain.TAG_READ_WRITE_ACCESS": 1, 135 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 136 | "servermain.TAG_SCALING_TYPE": 0 137 | }, 138 | { 139 | "common.ALLTYPES_NAME": "AI_4", 140 | "servermain.TAG_ADDRESS": "AnalogInput.4.PresentValue", 141 | "servermain.TAG_DATA_TYPE": 8, 142 | "servermain.TAG_READ_WRITE_ACCESS": 1, 143 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 144 | "servermain.TAG_SCALING_TYPE": 0 145 | }, 146 | { 147 | "common.ALLTYPES_NAME": "AI_5", 148 | "servermain.TAG_ADDRESS": "AnalogInput.5.PresentValue", 149 | "servermain.TAG_DATA_TYPE": 8, 150 | "servermain.TAG_READ_WRITE_ACCESS": 1, 151 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 152 | "servermain.TAG_SCALING_TYPE": 0 153 | } 154 | ] 155 | }, 156 | { 157 | "common.ALLTYPES_NAME": "ao", 158 | "tags": [ 159 | { 160 | "common.ALLTYPES_NAME": "AO_0", 161 | "servermain.TAG_ADDRESS": "AnalogOutput.0.PresentValue", 162 | "servermain.TAG_DATA_TYPE": 8, 163 | "servermain.TAG_READ_WRITE_ACCESS": 1, 164 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 165 | "servermain.TAG_SCALING_TYPE": 0 166 | }, 167 | { 168 | "common.ALLTYPES_NAME": "AO_1", 169 | "servermain.TAG_ADDRESS": "AnalogOutput.1.PresentValue", 170 | "servermain.TAG_DATA_TYPE": 8, 171 | "servermain.TAG_READ_WRITE_ACCESS": 1, 172 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 173 | "servermain.TAG_SCALING_TYPE": 0 174 | }, 175 | { 176 | "common.ALLTYPES_NAME": "AO_2", 177 | "servermain.TAG_ADDRESS": "AnalogOutput.2.PresentValue", 178 | "servermain.TAG_DATA_TYPE": 8, 179 | "servermain.TAG_READ_WRITE_ACCESS": 1, 180 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 181 | "servermain.TAG_SCALING_TYPE": 0 182 | }, 183 | { 184 | "common.ALLTYPES_NAME": "AO_3", 185 | "servermain.TAG_ADDRESS": "AnalogOutput.3.PresentValue", 186 | "servermain.TAG_DATA_TYPE": 8, 187 | "servermain.TAG_READ_WRITE_ACCESS": 1, 188 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 189 | "servermain.TAG_SCALING_TYPE": 0 190 | }, 191 | { 192 | "common.ALLTYPES_NAME": "AO_4", 193 | "servermain.TAG_ADDRESS": "AnalogOutput.4.PresentValue", 194 | "servermain.TAG_DATA_TYPE": 8, 195 | "servermain.TAG_READ_WRITE_ACCESS": 1, 196 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 197 | "servermain.TAG_SCALING_TYPE": 0 198 | }, 199 | { 200 | "common.ALLTYPES_NAME": "AO_5", 201 | "servermain.TAG_ADDRESS": "AnalogOutput.5.PresentValue", 202 | "servermain.TAG_DATA_TYPE": 8, 203 | "servermain.TAG_READ_WRITE_ACCESS": 1, 204 | "servermain.TAG_SCAN_RATE_MILLISECONDS": 100, 205 | "servermain.TAG_SCALING_TYPE": 0 206 | } 207 | ] 208 | } 209 | ] 210 | } 211 | ] 212 | } -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/kepware_object_snippets/templates/Single IoT Item - Kepware JSON Snippet.json: -------------------------------------------------------------------------------- 1 | { 2 | "iot_gateway.IOT_ITEM_SERVER_TAG": "bacnetDevices.d1.ai1", 3 | "iot_gateway.IOT_ITEM_USE_SCAN_RATE": true, 4 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 60000, 5 | "iot_gateway.IOT_ITEM_SEND_EVERY_SCAN": true, 6 | "iot_gateway.IOT_ITEM_DEADBAND_PERCENT": 0, 7 | "iot_gateway.IOT_ITEM_ENABLED": true, 8 | "iot_gateway.IOT_ITEM_DATA_TYPE": 8 9 | } -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/kepware_object_snippets/templates/Single MQTT Agent for IoT Hub - Kepware JSON Snippet.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "Azure1", 3 | "iot_gateway.AGENTTYPES_TYPE": "MQTT Client", 4 | "iot_gateway.AGENTTYPES_ENABLED": false, 5 | "iot_gateway.MQTT_CLIENT_URL": "ssl://10.64.105.30:8883", 6 | "iot_gateway.MQTT_CLIENT_TOPIC": "devices/kepserver/messages/events/kepTest", 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": "kepserver", 17 | "iot_gateway.MQTT_CLIENT_USERNAME": "kepIoTEdgeTest201801.azure-devices.net/kepserver", 18 | "iot_gateway.MQTT_CLIENT_PASSWORD": "eb58acaae45b1fe4b3082fa45cc95111913e8e1dfcf29db241ed32bfc619366b104a0b5739b6006fec65ac006db8b7635267311b837d5f8a32f0f068c32da58d884f9721b1c4974dc9586976324a903dbc4792e8595455d067a8da94b7342c923b4d8bf8f1a74ae9e8a932552bcd48f48a2de59d8d2397d79488c87a15b727c88fa42e7062d1b2ae07d7a360cd9e199a435af7b499b1c42c2b5e38775d617b5b66e4c9b68dcd6d902304ca0f8b7f61eb5cfb1bb7f109bbf7", 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 | } -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/kepware_object_snippets/templates/Single Tag - Kepware JSON Snippet.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "AO_5", 3 | "servermain.TAG_ADDRESS": "AnalogOutput.5.PresentValue", 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 | } -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_and_azure/setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "configApiUsername": "administrator", 3 | "configApiPassword": "", 4 | "name": "device", 5 | "hubName": "myCloudHub1", 6 | "sasLen": 3600, 7 | "agentFile": "kepware_object_snippets/agents.json" 8 | } -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_for_BACnetIP/Readme.md: -------------------------------------------------------------------------------- 1 | # Build BACnet/IP devices and Deploy and Map IoT Agents to devices with Azure IoT Hub 2 | 3 | This script automatically creates a complete Kepware project file based on a list of BACnet data points and integrates the BACnet/IP channel and all devices to Azure IoT Hub. The created project file will contain one BACnet channel, up to 128 BACnet devices below that channel, an IoT Gateway MQTT agent, and any number of tags/MQTT agent IoT item references. The IoT Gateway MQTT agent serves as an integration point to a single IoT Device created within an Azure IoT Hub. 4 | 5 | ## Dependencies 6 | 7 | - [Azure CLI 2.x.x](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) 8 | - [Azure CLI IoT Extension](https://github.com/Azure/azure-iot-cli-extension) (use the CLI to run 'az extension add --name azure-iot') 9 | - [Python 3.7 or above](https://www.python.org/downloads/) 10 | - Python [Requests](https://pypi.org/project/requests/) library 11 | 12 | ## Instructions 13 | 14 | After installing Python and the 'requests' library, navigate to the 'autodeploy_kepware_for_BACnetIP' folder and double-click the Python script "auto_deploy.py". To observe verbose script output in a command prompt, call the script via the command line via "python auto_deploy.py" 15 | 16 | ## Notes 17 | 18 | Review [auto_deploy.py](auto_deploy.py) for procedural details 19 | 20 | - Assignment of user parameters is conducted through [setup.json](setup.json) 21 | - BACnet device and point parameters are defined in [/csvs/source.csv](csvs/source.csv) 22 | - Currently only the following BACnet data point types (called "object types") are supported: Analog Inputs, Analog Outputs, Analog Values, Binary Inputs, Binary Outputs, and Binary Values 23 | - Azure connectivity (MQTT agent configuration) is controlled by / modified via the JSON object file at [/objs/agent.json](objs/agent.json). This JSON object file also includes a configuration of desired message format. 24 | - BACnet network number is assumed (and thus hard-coded) to be "1", which yields Device Instance addresses like "1.1210100". 25 | - BACnet device discovery is assumed (and thus hard-coded) to be "automatic via WhoIs/IAm", which means no IP address information is required, but that the devices need to support this BACnet service. Otherwise, the script should be run first and then the IP addresses / discovery type modified afterwards either manually via the Kepware Configuration Tool or via the Configuration API 26 | - If more than 128 BACnet devices are identified in the source CSV file, only 128 will be created. The value of '128' is the "device" limit per single channel for Kepware. 27 | -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_for_BACnetIP/auto_deploy.py: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # ------------------------------------------------------------------------- 3 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 4 | # See License.txt in the project root for 5 | # license information. 6 | # -------------------------------------------------------------------------- 7 | # Name: 8 | # auto_deploy 9 | # 10 | # Procedure: 11 | # 1- Ingests a setup file (setup.json) to determine values for the following critical variables: 12 | # -- Kepware Config API username ('administrator' by default) 13 | # -- Kepware Config API password (blank by default) 14 | # -- Kepware channel name ('B121' by default) 15 | # -- source CSV file path ('/csvs/source.csv' by default) 16 | # 2- Creates a single BACnet channel and a single IoT Gateway MQTT agent from JSON object templates 17 | # located in the script's 'objs' local directory 18 | # 3- Ingests a CSV file located in the script's 'csvs' local directory 19 | # ex. Device, Name, ObjectType, Instance 20 | # 1210100, AV_0, 2, 0 21 | # 22 | # This yields Device with name "1210100" and tag with name "AV_0" and address of 23 | # "AnalogValue.0.PresentValue" and an IoT item reference to 24 | # ".." i.e. B121.1210100.AV_0 25 | # 26 | # ******************************************************************************/ 27 | 28 | import csv 29 | import json 30 | import requests 31 | 32 | 33 | def get_parameters(setup_file): 34 | try: 35 | print("Loading 'setup.json' from local directory") 36 | with open(setup_file) as j: 37 | setup_data = json.load(j) 38 | print("-- Load succeeded") 39 | return setup_data 40 | except Exception as e: 41 | print("-- Load setup failed - '{}'".format(e)) 42 | return False 43 | 44 | 45 | def convert_csv_to_json(path): 46 | try: 47 | print("Converting CSV at '{}' into JSON".format(path)) 48 | csv_file = open(path, 'r') 49 | reader = csv.DictReader(csv_file) 50 | out = json.dumps([row for row in reader]) 51 | json_from_csv = json.loads(out) 52 | print("-- Conversion succeeded") 53 | return json_from_csv 54 | except Exception as e: 55 | print("-- Conversion failed - '{}'".format (e)) 56 | return False 57 | 58 | 59 | def get_templates(): 60 | try: 61 | print("Loading channel, device, and tag JSON template from local directory") 62 | c_template = open('./objs/channel.json') 63 | d_template = open('./objs/device.json') 64 | t_template = open('./objs/tag.json') 65 | a_template = open('./objs/agent.json') 66 | a_item_template = open('./objs/agent_item.json') 67 | chan = json.load(c_template) 68 | dev = json.load(d_template) 69 | tag = json.load(t_template) 70 | agent = json.load(a_template) 71 | a_item_template = json.load(a_item_template) 72 | print("-- Load succeeded") 73 | return chan, dev, tag, agent, a_item_template 74 | except Exception as e: 75 | print("-- Load failed - '{}'".format(e)) 76 | return False 77 | 78 | 79 | def get_unique_devices(master_list): 80 | try: 81 | print("Checking for unique devices in JSON from converted CSV") 82 | key = 'Device' 83 | seen = set() 84 | seen_add = seen.add 85 | unique_devices = [x for x in master_list if x[key] not in seen and not seen_add(x[key])] 86 | print("-- Check succeeded, unique devices gathered") 87 | return unique_devices 88 | except Exception as e: 89 | print("-- Load failed - '{}'".format(e)) 90 | return False 91 | 92 | 93 | def make_devices(devices, tchan, tdev, tagent, dev_limit, user, passw): 94 | try: 95 | print ("Creating BACnet channel, unique gathered devices, and IoT Gateway MQTT agent") 96 | cname = tchan['common.ALLTYPES_NAME'] 97 | tagent['common.ALLTYPES_NAME'] = cname 98 | agent_endpoint = 'http://{}:{}@127.0.0.1:57412/config/v1/project/_iot_gateway/mqtt_clients'.format (user, passw) 99 | channel_endpoint = 'http://{}:{}@127.0.0.1:57412/config/v1/project/channels/'.format(user, passw) 100 | device_endpoint = 'http://{}:{}@127.0.0.1:57412/config/v1/project/channels/{}/devices/.'.format(user, passw, cname) 101 | rpchan = requests.post(url=channel_endpoint, json=tchan) 102 | rpchan.raise_for_status() 103 | rpagent = requests.post (url=agent_endpoint, json=tagent) 104 | rpagent.raise_for_status () 105 | 106 | def make_device_list(): 107 | device_list = [] 108 | add_dev = tdev 109 | x = 0 110 | for i in devices: 111 | d_name = i['Device'] 112 | add_dev['common.ALLTYPES_NAME'] = d_name 113 | add_dev['servermain.DEVICE_ID_STRING'] = "1.{}".format(d_name) 114 | device_list.append(add_dev.copy()) 115 | x += 1 116 | if x == dev_limit: 117 | break 118 | return device_list 119 | 120 | out = make_device_list() 121 | 122 | rpdev = requests.post(url=device_endpoint, json=out) 123 | rpdev.raise_for_status() 124 | print ("-- Channel and devices and agent created") 125 | return 126 | 127 | except requests.exceptions.HTTPError as err: 128 | print("-- POSTs failed - '{}'".format(err)) 129 | return False 130 | 131 | 132 | def make_tags(master_list, devices, tchan, ttag, user, passw, limit, titem): 133 | try: 134 | print ("Creating devices tags and iot item references") 135 | x = 0 136 | # obtain and review each unique device ID found in converted CSV 137 | for i in devices: 138 | add_tag = ttag 139 | c_name = tchan['common.ALLTYPES_NAME'] 140 | add_iot_item = titem 141 | d_name = i['Device'] 142 | device_tag_list = [] 143 | agent_tag_list = [] 144 | 145 | # for each tag (item) present in converted CSV associated with the current device ID, build device tag and iot item tag reference ready for posting to Kepware 146 | for item in master_list: 147 | if item['Device'] == d_name: 148 | t_name = item['Name'] 149 | instance = item['Instance'] 150 | 151 | # based on the name of the item in the converted CSV, make custom tag address per driver requirements and also make an iot item reference 152 | def make_address(): 153 | if "AI" in t_name: 154 | add_tag['servermain.TAG_ADDRESS'] = 'AnalogInput.' + instance + '.PresentValue' 155 | add_tag['servermain.TAG_DATA_TYPE'] = 8 156 | add_iot_item['iot_gateway.IOT_ITEM_SERVER_TAG'] = "{}.{}.{}".format(c_name, d_name, t_name) 157 | else: 158 | if "AV" in t_name: 159 | add_tag['servermain.TAG_ADDRESS'] = 'AnalogValue.' + instance + '.PresentValue' 160 | add_tag['servermain.TAG_DATA_TYPE'] = 8 161 | add_iot_item['iot_gateway.IOT_ITEM_SERVER_TAG'] = "{}.{}.{}".format (c_name, d_name, t_name) 162 | else: 163 | if "BI" in t_name: 164 | add_tag['servermain.TAG_ADDRESS'] = 'BinaryInput.' + instance + '.PresentValue' 165 | add_tag['servermain.TAG_DATA_TYPE'] = 1 166 | add_iot_item['iot_gateway.IOT_ITEM_SERVER_TAG'] = "{}.{}.{}".format (c_name, d_name, t_name) 167 | else: 168 | if "BO" in t_name: 169 | add_tag['servermain.TAG_ADDRESS'] = 'BinaryOutput.' + instance + '.PresentValue' 170 | add_tag['servermain.TAG_DATA_TYPE'] = 1 171 | add_iot_item['iot_gateway.IOT_ITEM_SERVER_TAG'] = "{}.{}.{}".format (c_name, d_name,t_name) 172 | else: 173 | if "BV" in t_name: 174 | add_tag['servermain.TAG_ADDRESS'] = 'BinaryValue.' + instance + '.PresentValue' 175 | add_tag['servermain.TAG_DATA_TYPE'] = 1 176 | add_iot_item['iot_gateway.IOT_ITEM_SERVER_TAG'] = "{}.{}.{}".format (c_name, d_name, t_name) 177 | else: 178 | pass 179 | return 180 | 181 | make_address() 182 | add_tag['common.ALLTYPES_NAME'] = t_name 183 | 184 | # add device tag and iot item tag references to lists 185 | agent_tag_list.append(add_iot_item.copy()) 186 | device_tag_list.append(add_tag.copy()) 187 | 188 | # define the unique device's tag addition endpoint 189 | tag_endpoint = 'http://{}:{}@127.0.0.1:57412/config/v1/project/channels/{}/devices/{}/tags/'.format(user, passw, c_name, d_name) 190 | else: 191 | pass 192 | 193 | # post list of device tags to dynamic endpoints 194 | rptag = requests.post (url=tag_endpoint, json=device_tag_list) 195 | 196 | # as tag sets are built for each device, post the device's list of iot item references to agent 197 | posted_agent_endpoint = 'http://{}:{}@127.0.0.1:57412/config/v1/project/_iot_gateway/mqtt_clients/{}/iot_items/'.format (user, passw, c_name) 198 | rpagent_items = requests.post(url=posted_agent_endpoint, json = agent_tag_list) 199 | 200 | # since we're parsing through all unique devices, stop at a maximum based on number of devices allowed per channel 201 | x += 1 202 | if x == limit: 203 | break 204 | 205 | print ("-- Device tags and IoT item references created") 206 | return rptag, rpagent_items 207 | except Exception as e: 208 | print ("-- Device tag and iot item reference creation failed - '{}'".format (e)) 209 | return False 210 | 211 | 212 | # load setup parameters 213 | setupFilePath = 'setup.json' 214 | setupData = get_parameters(setupFilePath) 215 | 216 | # assign global variables 217 | user = setupData['configApiUsername'] 218 | passw = setupData['configApiPassword'] 219 | 220 | # set limit of devices based on device-per-channel limit in Kepware 221 | device_limit = 128 222 | 223 | # get json templates for channel, device, and tag 224 | jChan, jDev, jTag, jAgent, jAgentItem = get_templates() 225 | 226 | # convert CSV file to JSON 227 | csv_file_path = setupData['path'] 228 | masterList = convert_csv_to_json(csv_file_path) 229 | 230 | # obtain list of unique devices from converted csv 231 | uniqueDevices = get_unique_devices(masterList) 232 | 233 | # create devices and MQTT agent 234 | make_devices(uniqueDevices, jChan, jDev, jAgent, device_limit, user, passw) 235 | 236 | # add tags to devices and iot item references to agent 237 | make_tags(masterList, uniqueDevices, jChan, jTag, user, passw, device_limit, jAgentItem) 238 | -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_for_BACnetIP/objs/agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "b121fmKepware", 3 | "iot_gateway.AGENTTYPES_TYPE": "MQTT Client", 4 | "iot_gateway.AGENTTYPES_ENABLED": true, 5 | "iot_gateway.MQTT_CLIENT_URL": "ssl://SmartBuildingsIoTHub121.azure-devices.net:8883", 6 | "iot_gateway.MQTT_CLIENT_TOPIC": "devices/KepwareGateway/messages/events/", 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": 1, 11 | "iot_gateway.AGENTTYPES_TIMEOUT_S": 5, 12 | "iot_gateway.AGENTTYPES_MESSAGE_FORMAT": 1, 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": "|#each VALUES|\r\n{\"gwy\": \"B121Kepware\", \"name\": \"|TAGNAME|\", \"value\": |VALUE|, \"status\": |QUALITY|, \"value timestamp\": |TIMESTAMP|, \"publish timestamp\": \"|SERVERDATE|\" } |#unless @last|,|/unless|\r\n|/each|\r\n\r\n", 16 | "iot_gateway.MQTT_CLIENT_CLIENT_ID": "KepwareGateway", 17 | "iot_gateway.MQTT_CLIENT_USERNAME": "SmartBuildingsIoTHub121.azure-devices.net/KepwareGateway", 18 | "iot_gateway.MQTT_CLIENT_PASSWORD": "5e3ce384700e2b29e8bc9ac4dc0048db3aa867ff9078f3e587c7eb5b143ed4d76e570130b95fe3566e7fb67ece202134b454dee20afe242506328eb5ca8c2e9c53291e8d80d14200f8c1140a7e0e90fc9c788d8aa19b93edb642faac550e815d57246a0292ca16c6436824a4aca7ca53d6b53a9bb207cf57412a8660696eaee14bb0e584c16fc66ff7bd931eab214796987826f8f8f491583aba9d047318205b4db693f2fb260f9fc433bff6b7610c2168e927ce497bd6dc", 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 | -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_for_BACnetIP/objs/agent_item.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "System__ActiveTagCount", 3 | "iot_gateway.IOT_ITEM_SERVER_TAG": "_System._ActiveTagCount", 4 | "iot_gateway.IOT_ITEM_USE_SCAN_RATE": true, 5 | "iot_gateway.IOT_ITEM_SCAN_RATE_MS": 1000, 6 | "iot_gateway.IOT_ITEM_SEND_EVERY_SCAN": false, 7 | "iot_gateway.IOT_ITEM_ENABLED": true 8 | } -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_for_BACnetIP/objs/channel.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "B121", 3 | "common.ALLTYPES_DESCRIPTION": "", 4 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "BACnet/IP", 5 | "servermain.CHANNEL_DIAGNOSTICS_CAPTURE": false, 6 | "servermain.CHANNEL_UNIQUE_ID": 3171198964, 7 | "servermain.CHANNEL_ETHERNET_COMMUNICATIONS_NETWORK_ADAPTER_STRING": "", 8 | "servermain.CHANNEL_WRITE_OPTIMIZATIONS_METHOD": 2, 9 | "servermain.CHANNEL_WRITE_OPTIMIZATIONS_DUTY_CYCLE": 10, 10 | "servermain.CHANNEL_NON_NORMALIZED_FLOATING_POINT_HANDLING": 1, 11 | "bacnet.CHANNEL_ALLOW_COV_NOTIFICATIONS": 0, 12 | "bacnet.CHANNEL_UDP_PORT": 47808, 13 | "bacnet.CHANNEL_NETWORK_NUMBER": 1, 14 | "bacnet.CHANNEL_DEVICE_INSTANCE": 0, 15 | "bacnet.CHANNEL_FOREIGN_DEVICE": 0, 16 | "bacnet.CHANNEL_IP_ADDRESS_OF_REMOTE_BBMD": "0.0.0.0", 17 | "bacnet.CHANNEL_REGISTRATION_TIME_TO_LIVE": 60 18 | } -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_for_BACnetIP/objs/device.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "Device1", 3 | "servermain.MULTIPLE_TYPES_DEVICE_DRIVER": "BACnet/IP", 4 | "servermain.DEVICE_ID_STRING": "1.100" 5 | } -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_for_BACnetIP/objs/tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "common.ALLTYPES_NAME": "AI0", 3 | "servermain.TAG_ADDRESS": "AnalogInput.0.PresentValue", 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 | -------------------------------------------------------------------------------- /IoT Gateway/Azure IoT Hub Integration/autodeploy_kepware_for_BACnetIP/setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "configApiUsername": "administrator", 3 | "configApiPassword": "", 4 | "channel_name": "channel1", 5 | "path": "csvs/source.csv" 6 | } -------------------------------------------------------------------------------- /IoT Gateway/Google Cloud Integration/README.md: -------------------------------------------------------------------------------- 1 | # Create a JSON Web Token (JWT) and Update MQTT Agent 2 | 3 | This script creates a JWT based on the parameters satisfied in the code and then updates the password field of MQTT Agent with created JWT according to parameters of Configuration API. 4 | 5 | ## Dependencies 6 | 7 | - [Kepware Configuration API SDK for Python](https://github.com/PTCInc/Kepware-ConfigAPI-SDK-Python) 8 | - [PyJWT library](https://pyjwt.readthedocs.io/en/stable/) 9 | 10 | ## Notes 11 | 12 | - This script is here to support [Kepware MQTT Agent and Google Cloud IoT Core](https://www.kepware.com/getattachment/f927bc2c-8a2b-459e-90ed-c85b29fdfffd/Kepware-MQTT-Agent-and-Google-IoT-Core.pdf) connectivity guide. 13 | - The private key file that is used to create JWT should be created prior the use of this script. 14 | - MQTT Agent should be created prior the use of this script according to connectivity guide. 15 | - Configuration API should be enabled and available for connections in order to use this script. 16 | -------------------------------------------------------------------------------- /IoT Gateway/Google Cloud Integration/Update JWT and MQTT Agent.py: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # ------------------------------------------------------------------------- 3 | # Copyright (c) PTC Inc. and/or all its affiliates. All rights reserved. 4 | # See License.txt in the project root for 5 | # license information. 6 | # -------------------------------------------------------------------------- 7 | # Description: 8 | # This script creates a JWT based on the parameters specified such as 9 | # Google Cloud project ID and private key file and then updates the 10 | # password field of desired MQTT Agent. 11 | # 12 | # Procedure: 13 | # Create a JWT 14 | # Update MQTT AGent with creted JWT 15 | # 16 | # ******************************************************************************/ 17 | 18 | from kepconfig import connection, error 19 | import kepconfig.iot_gateway as IoT 20 | from kepconfig.iot_gateway import agent, iot_items 21 | import datetime 22 | import jwt 23 | 24 | # Agent name and Type to be used - constants from kepconfig.iotgateway 25 | # can be used to identify the type of agent 26 | agent_name = 'IoTCore' 27 | agent_type = IoT.MQTT_CLIENT_AGENT 28 | 29 | # JWT specific variables, Google Cloud Project ID, private key file location and name 30 | project_id='coastal-throne-266515' 31 | private_key_file='rsa_private.pem' 32 | 33 | # [START iot_mqtt_jwt] 34 | def create_jwt(project_id, private_key_file, algorithm): 35 | 36 | token = { 37 | # The time that the token was issued at 38 | 'iat': datetime.datetime.utcnow(), 39 | # The time the token expires. 40 | 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=120), 41 | # The audience field should always be set to the GCP project id. 42 | 'aud': project_id 43 | } 44 | 45 | # Read the private key file. 46 | with open(private_key_file, 'r') as f: 47 | private_key = f.read() 48 | 49 | print('Creating JWT using {} from private key file {}'.format(algorithm, private_key_file)) 50 | 51 | return jwt.encode(token, private_key, algorithm=algorithm) 52 | # [END iot_mqtt_jwt] 53 | 54 | # Create JWT and Convert into String 55 | jwt=create_jwt(project_id, private_key_file, algorithm='RS256').decode("utf-8") 56 | 57 | def HTTPErrorHandler(err): 58 | # Generic Handler for exception errors 59 | if err.__class__ is error.KepHTTPError: 60 | print(err.code) 61 | print(err.msg) 62 | print(err.url) 63 | print(err.hdrs) 64 | print(err.payload) 65 | elif err.__class__ is error.KepURLError: 66 | print(err.url) 67 | print(err.reason) 68 | else: 69 | print('Different Exception Received: {}'.format(err)) 70 | 71 | # This creates a server reference that is used to target all modifications of 72 | # the Kepware configuration 73 | server = connection.server(host = '127.0.0.1', port = 57412, user = 'Administrator', pw = '') 74 | 75 | # Modify the password of the Agent 76 | agent_data = { 77 | } 78 | agent_data['iot_gateway.MQTT_CLIENT_PASSWORD'] = jwt 79 | try: 80 | print("{} - {}".format("Modify the password in the MQTT Agent", agent.modify_iot_agent(server,agent_data, agent_name, agent_type))) 81 | except Exception as err: 82 | HTTPErrorHandler(err) 83 | -------------------------------------------------------------------------------- /IoT Gateway/README.md: -------------------------------------------------------------------------------- 1 | # IoT Gateway Examples 2 | -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/Kepware_IoTFunction_demo.js: -------------------------------------------------------------------------------- 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 | //IoT Gateway Functions Demo - NodeJS 7 | const kepwareIotGateway = require('../Kepware_iotgateway_functions'); 8 | 9 | //Config Param 10 | var kseIp = '127.0.0.1'; 11 | var iotGatewayPort = 39320; 12 | //Make use of Security Policies and User manager to enhance security 13 | var user = 'Administrator'; 14 | var pw = ''; 15 | 16 | //--------------INITIALIZATION--------------// 17 | 18 | //Browse the server and ETL the returned json for reading latere 19 | //Standard browse 20 | kepwareIotGateway.browseRestServer(user, pw, kseIp, iotGatewayPort, (data) => console.log('[Browse JSON Return] : ' + data + '\n')); 21 | //Gets listing of items and executes a read of the list of items 22 | kepwareIotGateway.getIoTServerTags(user, pw, kseIp, iotGatewayPort, function(data) { 23 | console.log('[Reformed in browse REST Server] : ' + data + '\n'); 24 | var readAry = data; 25 | kepwareIotGateway.readTags(user, pw, kseIp, iotGatewayPort, readAry, (data) => console.log('[Read JSON Return] : ' 26 | + JSON.stringify(data))); 27 | }); 28 | 29 | //Writes unix timestamp to Tag "bom_gov.scriptwriteback.timestamp_last" - Could be used as a heartbeat signal 30 | kepwareIotGateway.writeTags(user, pw, kseIp, iotGatewayPort, [{ "id": "bom_gov.scriptwriteback.timestamp_last", "v": Date.now() }]); -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/README.md: -------------------------------------------------------------------------------- 1 | # Generic Read and Write with IoT Gateway REST server Interface 2 | 3 | This demo aims to demonstrate the basic, Browse, Read, Write command. The [Kepware_IoTFunction_demo.js](Kepware_IoTFunction_demo.js) uses the HTTP Request to browse the IoT Server on Kepware. After which, we make use of use the data to form an array of tagnames for issuing the HTTP Post back to Kepware for a read operation. Asynchronously, we write a unix timestamp back to Kepware. 4 | 5 | **Note:** In the sample, the module [Kepware_iotgateway_functions.js](../Kepware_iotgateway_functions.js) is used for common source codes sharing with other examples. Change the require dir to ```const kepwareIotGateway = require('./Kepware_iotgateway_functions');``` in the script if using the module in the same folder. 6 | 7 | ![Pseudo Code Diagram](pics/diagram.png) 8 | 9 | ## Setup Kepware 10 | 11 | 1. Load the provided sample file [NodeJsSample.opf](../NodeJsSample.opf) 12 | 2. Ensure that [Request HTTP Client](https://github.com/request/request) is installed in the same directory as the [Kepware_iotgateway_functions.js](../Kepware_iotgateway_functions.js) file is located 13 | - To install, navigate to the folder where the script is located 14 | - Enter ```npm install request``` to install the required module 15 | 3. Navigate to the proper folder and enter ```node ./Kepware_IoTFunction_demo.js``` in command prompt 16 | 17 | ## Results from IoT Gateway 18 | 19 | ![Results](pics/output.png) 20 | 21 | ## Timestamp Writeback 22 | 23 | Monitor the value seen below in Quick Client to see the value of the writeback. 24 | 25 | ![Quick Client View](pics/qc.png) 26 | -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/pics/Instructions - Generic IoT Demo.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PTCInc/Kepware-Example-Scripts/02bbe7c6c5dd2e07e71ba5dde9717932131d4dee/IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/pics/Instructions - Generic IoT Demo.docx -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/pics/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PTCInc/Kepware-Example-Scripts/02bbe7c6c5dd2e07e71ba5dde9717932131d4dee/IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/pics/diagram.png -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/pics/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PTCInc/Kepware-Example-Scripts/02bbe7c6c5dd2e07e71ba5dde9717932131d4dee/IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/pics/output.png -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/pics/qc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PTCInc/Kepware-Example-Scripts/02bbe7c6c5dd2e07e71ba5dde9717932131d4dee/IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Generic_Read_Write/pics/qc.png -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Kepware_iotgateway_functions.js: -------------------------------------------------------------------------------- 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 | // Provides generic functions used to interact with the IoT Gateway REST server interface. 8 | 9 | //requires request module. Install with "npm install request" 10 | const request = require('request'); 11 | 12 | //Kepware CRUD 13 | module.exports.browseRestServer = function(username, pw, ip, port, callback) { 14 | var auth = 'Basic ' + Buffer.from(username + ':' + pw).toString('base64'); 15 | request({ 16 | url: 'http://' + ip + ':' + port + '/iotgateway/browse', //Refer to documentation of API under http:///iotgateway 17 | method: "GET", 18 | headers: { 19 | "content-type": "application/json", 20 | "Authorization": auth, 21 | }, 22 | }, function(error, response, body) { 23 | if (error) { 24 | console.log("[ERROR] : " + error); 25 | }; 26 | //console.log("[KEPWARE] : " + body); 27 | if (typeof(callback) === "function") { 28 | callback(body); 29 | } 30 | }); 31 | }; 32 | 33 | module.exports.readTags = function(username, pw, ip, port, tagArray, callback) { 34 | //["Channel1.Device1.Tag1", "Channel1.Device1.Tag2"] - array example to read two tags 35 | var auth = 'Basic ' + Buffer.from(username + ':' + pw).toString('base64'); 36 | request({ 37 | url: 'http://' + ip + ':' + port + '/iotgateway/read', 38 | json: true, 39 | method: "POST", 40 | headers: { 41 | "content-type": "application/json", 42 | "Authorization": auth, 43 | }, 44 | json: tagArray, 45 | }, function(error, response, body) { 46 | if (error) { 47 | console.log("[ERROR] : " + error); 48 | }; 49 | //console.log("[KEPWARE] : "); 50 | //console.log(body); 51 | if (typeof(callback) === "function") { 52 | callback(body); 53 | } 54 | }); 55 | }; 56 | 57 | module.exports.writeTags = function(username, pw, ip, port, tagArray, callback) { 58 | //[{"id" : "Channel1.Device1.Tag1", "v": "Value to write"},{"id" : "Channel1.Device1.Tag2", "v": "Value to write"}] 59 | var auth = 'Basic ' + Buffer.from(username + ':' + pw).toString('base64'); 60 | request({ 61 | url: 'http://' + ip + ':' + port + '/iotgateway/write', //Requires explicit enabling during REST Server Set up 62 | json: true, 63 | method: "POST", 64 | headers: { 65 | "content-type": "application/json", 66 | "Authorization": auth, 67 | }, 68 | json: tagArray, 69 | }, function(error, response, body) { 70 | if (error) { 71 | console.log("[ERROR] : " + error); 72 | }; 73 | //console.log("[KEPWARE] : "); 74 | //console.log(body); 75 | if (typeof(callback) === "function") { 76 | callback(); 77 | } 78 | }); 79 | }; 80 | 81 | //Additional Functions 82 | module.exports.getIoTServerTags = function(username, pw, ip, port, callback) { 83 | var auth = 'Basic ' + Buffer.from(username + ':' + pw).toString('base64'); 84 | request({ 85 | url: 'http://' + ip + ':' + port + '/iotgateway/browse', //Refer to documentation of API under http:///iotgateway 86 | method: "GET", 87 | headers: { 88 | "content-type": "application/json", 89 | "Authorization": auth, 90 | }, 91 | }, function(error, response, body) { 92 | var tempObj = JSON.parse(body); 93 | var tempAry = []; 94 | for (var i = 0; i < tempObj['browseResults'].length; i++) { 95 | //console.log(tempObj['browseResults'][i]['id']); 96 | tempAry.push(tempObj['browseResults'][i]['id']); 97 | } 98 | if (error) { 99 | console.log("[ERROR] : " + error); 100 | }; 101 | if (typeof(callback) === "function") { 102 | callback(tempAry); 103 | } 104 | }); 105 | }; -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Loading_Data_third_party_API_to_Kepware/Kepware_IotGW_App.js: -------------------------------------------------------------------------------- 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 | //Description: This app issues a HTTP GET from a third party API, flattens the structure and loads the data into Kepware via HTTP Post to a REST Server in IoT Gateway 9 | //1. Get the data from endpoint 10 | //2. Take the data and extract only the data 11 | //3. From observation of sample, index 0 is the latest 12 | //4. Break object in index 0 and write to Kepware 13 | //5. Repeat at desired sampling rate 14 | //===================================================== 15 | 16 | 17 | var request = require('request'); 18 | const kepwareIotGateway = require('../Kepware_iotgateway_functions'); 19 | var kseIp = '127.0.0.1'; 20 | var iotGatewayPort = 39320; 21 | 22 | //Make use of Security Policies and User manager to enhance security 23 | var user = 'Administrator'; 24 | var pw = ''; 25 | 26 | var samplingRate = 5000; //5 seconds 27 | var ChannelDeviceString = 'bom_gov.json.'; 28 | var timeNow = Date.now(); 29 | 30 | var options = { 31 | url: 'http://www.bom.gov.au/fwo/IDN60901/IDN60901.94767.json', 32 | 'keepAlive': 'true', 33 | headers: { 34 | 'User-Agent': 'request' 35 | } 36 | } 37 | 38 | getBomData = function() { 39 | //1. Get the data from endpoint 40 | request.get(options, function(error, response, body) { 41 | console.log(body); 42 | var objTemp = JSON.parse(body); 43 | //2. Take the data and extract only the data && 3. From observation of sample, index 0 is the latest 44 | var latestObjData = objTemp["observations"]["data"][0]; 45 | console.log(JSON.stringify(latestObjData)); 46 | //4. Break object in index 0 and write to Kepware 47 | var jsonArray = returnArray(latestObjData); 48 | console.log(jsonArray); //output it 49 | kepwareIotGateway.writeTags(user, pw, kseIp, iotGatewayPort, jsonArray); 50 | 51 | }) 52 | }; 53 | 54 | returnArray = function(jsonData) { 55 | //Takes obj and returns the an array of JSONS required by Kepware 56 | // [{ "id": "Channel1.Device1.tag1", "v": 42 }] 57 | var jsonKeys = Object.keys(jsonData); 58 | var maxKey = jsonKeys.length; 59 | var string; 60 | var arry = []; 61 | for (var i = 0; i < maxKey; i++) { 62 | string = "" + '{"id" : "' + ChannelDeviceString + jsonKeys[i] + '", "v" : "' + jsonData[jsonKeys[i]] + '" }'; 63 | arry.push(JSON.parse(string)); 64 | } 65 | return arry; 66 | 67 | } 68 | 69 | getBomData(); 70 | setInterval(function() { 71 | getBomData(); 72 | }, samplingRate); //5. Repeat at desired sampling rate -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Loading_Data_third_party_API_to_Kepware/README.md: -------------------------------------------------------------------------------- 1 | # Write Data from third party API to a Simulation Driver with IoT Gateway REST server Interface 2 | 3 | This demo aims to demonstrate pulling data from a third party API, [Australia Weather Information](http://www.bom.gov.au/fwo/IDN60901/IDN60901.94767.json) and store the data into tags within Kepware Server. The [Kepware_IotGW_App.js](Kepware_IotGW_App.js) uses the HTTP Request to pull the data from the third party API, write the data to Kepware Server and continue this process at the defined interval. 4 | 5 | **Note:** In the sample, the module [Kepware_iotgateway_functions.js](../Kepware_iotgateway_functions.js) is used for common source codes sharing with other examples. Change the require dir to ```const kepwareIotGateway = require('./Kepware_iotgateway_functions');``` in the script if using the module in the same folder. 6 | 7 | ![Pseudo Code Diagram](pics/diagram.png) 8 | 9 | ## Setup Kepware 10 | 11 | 1. Load the provided sample file [NodeJsSample.opf](../NodeJsSample.opf) 12 | 2. Ensure that [Request HTTP Client](https://github.com/request/request) is installed in the same directory as the [Kepware_iotgateway_functions.js](../Kepware_iotgateway_functions.js) and [Kepware_IotGW_App.js](Kepware_IotGW_App.js) files are located 13 | - To install, navigate to the folder where the script is located 14 | - Enter ```npm install request``` to install the required module 15 | 3. Navigate to the proper folder and enter ```node ./Kepware_IotGW_App.js``` in command prompt 16 | 17 | ## Results 18 | 19 | Monitor the values seen below in Quick Client to see the value written have been executed. 20 | 21 | ![Quick Client View](pics/qc.png) 22 | -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Loading_Data_third_party_API_to_Kepware/pics/Instructions - GET Third Party.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PTCInc/Kepware-Example-Scripts/02bbe7c6c5dd2e07e71ba5dde9717932131d4dee/IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Loading_Data_third_party_API_to_Kepware/pics/Instructions - GET Third Party.docx -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Loading_Data_third_party_API_to_Kepware/pics/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PTCInc/Kepware-Example-Scripts/02bbe7c6c5dd2e07e71ba5dde9717932131d4dee/IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Loading_Data_third_party_API_to_Kepware/pics/diagram.png -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Loading_Data_third_party_API_to_Kepware/pics/qc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PTCInc/Kepware-Example-Scripts/02bbe7c6c5dd2e07e71ba5dde9717932131d4dee/IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/Loading_Data_third_party_API_to_Kepware/pics/qc.png -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/NodeJsSample.opf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PTCInc/Kepware-Example-Scripts/02bbe7c6c5dd2e07e71ba5dde9717932131d4dee/IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/NodeJsSample.opf -------------------------------------------------------------------------------- /IoT Gateway/Read and Write Examples/Node.js/Read_Calc_Write Examples/README.md: -------------------------------------------------------------------------------- 1 | # NodeJS example with IoT Gateway REST agents 2 | 3 | This node.js apps provide examples on how to read and write values to Kepware using the IoT Gateway REST interfaces. The [Generic_Read_Write](Generic_Read_Write) reads values from the Kepware IoT Gateway REST server interface and writes values back to Kepware. 4 | 5 | The [Loading_Data_third_party_API_to_Kepware](Loading_Data_third_party_API_to_Kepware) example gathers data from a weather station API and sends the data to Kepware for other applications, such as SCADA/HMI, can receive the data from Kepware. 6 | 7 | There is a Kepware project file [NodeJsSample.opf](NodeJsSample.opf) that has the configuration ready to run the examples with. 8 | 9 | ## Dependencies 10 | 11 | - [Request HTTP Client](https://github.com/request/request) 12 | - [Node.js](https://nodejs.org/) 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kepware Example Scripts 2 | 3 | This repositiory is a collection of tools or example scripts that can be used with Kepware products (KEPServerEX, Thingworx Kepware Server or Thingworx Kepware Edge). Examples could include automatic deployment of Azure "devices" for Kepware connection to ingesting CSV exports from Datalogger to configure an instance through the Configuration API. 4 | 5 | Examples are orgainized by component within Kepware with readme files providing further detail on how to leverage these modules/examples. 6 | 7 | ## Issues 8 | 9 | As stated in the icense of this repository, these profiles are provided "as-is". For any questions or problems that are encountered, please open an Issue within the repository. See [Repository Guidelines](docs/Repo-Guidelines.md) for more information. 10 | 11 | ## Visit 12 | 13 | - [Kepware.com](https://www.kepware.com/) 14 | - [PTC.com](https://www.ptc.com/) 15 | -------------------------------------------------------------------------------- /docs/Repo-Guidelines.md: -------------------------------------------------------------------------------- 1 | # Repository Guidelines 2 | 3 | This repository is intended to provide end users access to example solutions to common use cases, collaborate and share as necessary, and share feedback on issues they may have. The repository is owned and maintained by the Kepware Solutions Consulting team at PTC. 4 | 5 | Because the examples for these use cases are provided "as-is" per the [licensing terms](../license.md) there is no guarenteed response whenever an Issue is reported or question posted. In some cases, the owners may reach out to you directly in response to an issue/question and coordinate other means to share data as needed. 6 | 7 | ## Versions of Script Files 8 | 9 | Script files have versions, which follow the pattern: \.\.\, where minor versions of a major version should be backwards compatible with each other. The \ designation is for updates to a script for issue resolution. 10 | 11 | ## Pull Requests 12 | 13 | Pull Requests can be created by any user wanting to contribute to the repo. The owners of the repo will approve pull requests as changes are reviewed and will determine if changes meet the intent of the repository. 14 | 15 | ## Issues and Discussions in Github 16 | 17 | The Issues and Discussions features of GitHub will be used as the primary means of communication. An issue may be a bug report or identifying an issue found with a script. When creating or working issues, contributors are encouraged to use permalinks to link to other files in a repo. Crosslinking of issues, when they are related, is also encouraged. When creating pull requests that resolve issues, include the Issue as a cross link in the PR. 18 | 19 | If there is a question about how to use a script, recommended feature request, or general inquiries should be made in the Discussions section of the repo. 20 | --------------------------------------------------------------------------------