### Install Maven on Windows Source: https://github.com/bcs120280/sitesync_optimization/blob/main/ignition-sdk_examples-main/readme.md Command to install Maven using the Chocolatey package manager. ```bash choco install maven ``` -------------------------------- ### Start MQTT Connection Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Starts the MQTT connection for a specified tenant. Returns a success message upon successful initiation. ```python # Start MQTT connection start_result = connections.mqtt.startMQTTConnection(tenantID=1) # Returns: {"messageType": "SUCCESS", "message": "MQTT connection started"} ``` -------------------------------- ### Clone the Ignition SDK Examples Repository Source: https://github.com/bcs120280/sitesync_optimization/blob/main/ignition-sdk_examples-main/readme.md Use this command to download the repository to your local machine. ```bash git clone https://github.com/inductiveautomation/ignition-sdk-examples.git ``` -------------------------------- ### Install Maven on macOS Source: https://github.com/bcs120280/sitesync_optimization/blob/main/ignition-sdk_examples-main/readme.md Command to install Maven using the Homebrew package manager. ```bash brew install maven ``` -------------------------------- ### Install Maven on Linux Source: https://github.com/bcs120280/sitesync_optimization/blob/main/ignition-sdk_examples-main/readme.md Command to install Maven using the apt package manager on Debian-based Linux distributions. ```bash sudo apt-get install maven ``` -------------------------------- ### Build Ignition Modules with Maven Source: https://github.com/bcs120280/sitesync_optimization/blob/main/ignition-sdk_examples-main/readme.md Run this command within an example directory to compile the project and generate the .modl file. ```bash mvn package ``` -------------------------------- ### List and Get Devices Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Retrieves a list of all registered devices for a given tenant or fetches details for a specific device using its DevEUI. The list includes network credentials and tag assignments. ```python # Get all devices for tenant tenantID = 1 devices = device.get.listDevices(tenantID) # Returns: [ # {"devEUI": "0004A30B001C1234", "name": "Sensor-01", "deviceModelID": 1, ...}, # {"devEUI": "0004A30B001C5678", "name": "Sensor-02", "deviceModelID": 2, ...} # ] # Get specific device by DevEUI device_info = device.get.getDevice("0004A30B001C1234") # Returns: {"devEUI": "0004A30B001C1234", "name": "Sensor-01", "metaData": {...}, ...} ``` -------------------------------- ### Get Configured MQTT Topics Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Retrieves the currently configured MQTT topics for a given broker ID. Returns the topic string. ```python # Get configured topics topic = connections.mqtt.getMqttTopics(brokerID=1) # Returns: "application/+/device/+/event/up" ``` -------------------------------- ### Get PI Integration Settings Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Retrieves the current PI integration settings, including prefix and source folder. ```python # Get PI integration settings settings = PIIntegration.settings.getSettings() ``` -------------------------------- ### Optimize Tag Reads with Batching Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Avoid N round-trips to the tag provider by batching readBlocking calls. This example shows how to read multiple tag data types in a single call. ```python # BEFORE (N round-trips): def getDataType(tagPath, tagName): dType = str(system.tag.readBlocking( ['{0}/{1}.dataType'.format(tagPath, tagName)])[0].value) ... for i in items: tagName = str(i['fullPath']).split('/')[-1] j = { ..., "dataType": getDataType(tagPath, tagName), ... } ``` ```python # AFTER (1 round-trip): def getDataTypes(tagPath, items): tagNames = [str(i['fullPath']).split('/')[-1] for i in items] paths = ['{0}/{1}.dataType'.format(tagPath, n) for n in tagNames] results = system.tag.readBlocking(paths) out = {} for n, qv in zip(tagNames, results): dType = str(qv.value) if "Float" in dType: out[n] = "Float32" elif "Int" in dType: out[n] = "int16" else: out[n] = dType return out ``` -------------------------------- ### Get Current MQTT Broker Settings Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Retrieves the current MQTT broker connection settings for a given tenant. Returns a dictionary with broker address, port, and TLS usage. ```python # Get current MQTT settings settings = connections.mqtt.getMqttSettings(tenantID=1) # Returns: {"brokerAddress": "mqtt.company.com", "brokerPort": "8883", "useTls": True, ...} ``` -------------------------------- ### Get Specific Device Model Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Retrieves details for a specific device model using its model ID. ```python # Get specific model model = decoders.model.getModel(modelID=1) ``` -------------------------------- ### Update Device Installation Location Tag Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Updates the installation location tag for a device using latitude and longitude coordinates. Requires the tag path, latitude, and longitude. ```python # Update installation location device.tagOperations.updateInstallLocationTag( "[default]Field/Site-A/Sensor-01", lat=29.7604, lon=-95.3698 ) ``` -------------------------------- ### Build All Modules on Unix Source: https://github.com/bcs120280/sitesync_optimization/blob/main/ignition-sdk_examples-main/readme.md Use this script in the base directory to build all modules simultaneously on Unix-based systems. ```bash buildall.sh ``` -------------------------------- ### List All Device Profiles Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Lists all available device profiles for a given tenant ID, including model name, manufacturer, and device type. ```python # List all device profiles profiles = decoders.model.listDeviceProfiles(tenantID=1) ``` -------------------------------- ### Load Existing Network Server Settings Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Loads the existing network server connection settings for a given tenant. This is useful for retrieving current configurations. ```python # Load existing settings settings = connections.networkserver.loadNetworkSettings(tenantID=1) ``` -------------------------------- ### Implement gateway logging in replaceDevice Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Replace perspective-based printing with a dedicated logger for gateway-scope visibility. ```python _log = system.util.getLogger("device.replaceDevice") _log.warn("Warning: Metadata transfer incomplete: " + message) ``` -------------------------------- ### Create New Device Profile Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Creates a new device profile (model) for a given tenant ID and model name. Returns the ID and name of the new model. ```python # Create new device profile new_model = decoders.model.addModel(tenantID=1, modelName="CustomSensor") ``` -------------------------------- ### Calculate Device Health Statistics Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Obtain device health statistics by first getting tag paths, then reading values, and finally calculating status summaries. Devices can be grouped by their status. ```python # Get tag paths from device list tagPaths = device.diagnostics.getTagPaths(devices) # Returns: ["[default]Field/Site-A/Sensor-01", "[default]Field/Site-A/Sensor-02", ...] ``` ```python # Read diagnostic values values = device.diagnostics.getValues(tagPaths) ``` ```python # Calculate status summary statuses = device.diagnostics.getStatusCalculations(values) # Returns: {"NotActivated": 5, "Operational": 150, "TimedOut": 3, "DecodeError": 1} ``` ```python # Get devices grouped by status status_paths = device.diagnostics.getStatusPaths(tagPaths, values) # Returns: { # -1: ["[default]Field/Site-A/NotActivated-01", ...], # Not Activated # 0: ["[default]Field/Site-A/Sensor-01", ...], # Operational # 3: ["[default]Field/Site-A/TimedOut-01", ...], # Timed Out # 4: ["[default]Field/Site-A/DecodeError-01", ...] # Decode Error # } ``` -------------------------------- ### Verify PI settings null handling Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Simulates a null return from the PI adapter to ensure getSettings handles unconfigured states without raising TypeError. ```python # Simulate null return — should return {} not raise TypeError import json class MockPiAdapter: def getSettings(self, key): return "null" # simulates unconfigured state ``` -------------------------------- ### Create Device Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Creates a new IoT device in the SiteSync database with network credentials and metadata. This function validates device parameters and registers the device with the configured network server. ```APIDOC ## POST /api/device/createDevice/saveDevice ### Description Creates a new IoT device in the SiteSync database with network credentials and metadata. This function validates device parameters and registers the device with the configured network server. ### Method POST ### Endpoint /api/device/createDevice/saveDevice ### Parameters #### Request Body - **devEUI** (string) - Required - 16-character Device EUI - **appEUI** (string) - Required - 16-character Application/Join EUI - **appKey** (string) - Required - 32-character Application Key - **name** (string) - Required - Device display name - **serialNumber** (string) - Optional - Serial number - **modelID** (integer) - Required - Device profile/model ID - **lat** (float) - Optional - Installation latitude - **lon** (float) - Optional - Installation longitude - **description** (string) - Optional - Device description - **provider** (string) - Required - Tag provider name - **tagPath** (string) - Required - Base tag path for device tags - **image** (string) - Optional - Base64 encoded device image - **user** (string) - Required - User creating device - **appID** (integer) - Required - Network server application ID - **tenantID** (integer) - Required - Multi-tenant organization ID ### Request Example ```json { "devEUI": "0004A30B001C1234", "appEUI": "0000000000000001", "appKey": "00112233445566778899AABBCCDDEEFF", "name": "PressureSensor-01", "serialNumber": "SN-2026-001", "modelID": 1, "lat": 29.7604, "lon": -95.3698, "description": "Wellhead pressure monitor", "provider": "default", "tagPath": "Field/Site-A", "image": "base64_encoded_image", "user": "installer@company.com", "appID": 0, "tenantID": 1 } ``` ### Response #### Success Response (200) - **messageType** (string) - Indicates the status of the operation (e.g., "SUCCESS") - **message** (string) - A confirmation message (e.g., "Device created successfully") - **devEUI** (string) - The EUI of the created device #### Response Example ```json { "messageType": "SUCCESS", "message": "Device created successfully", "devEUI": "0004A30B001C1234" } ``` ``` -------------------------------- ### Verify activateDevice instance creation Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Tests that createLimitedInstance executes without raising a NameError. ```python # createLimitedInstance should not raise NameError # (Run in Script Console with a known test tag path) try: result = device.activateDevice.createLimitedInstance( "[default]TestPath", "TestTemplate", "TestTag", "[default]SourcePath" ) print("activateDevice.createLimitedInstance: PASS (returned " + str(result) + ")") except NameError as e: print("activateDevice.createLimitedInstance: FAIL (NameError: " + str(e) + ")") ``` -------------------------------- ### Define function documentation in properties file Source: https://github.com/bcs120280/sitesync_optimization/blob/main/ignition-sdk_examples-main/scripting-function/readme.md Add function descriptions and return value details to the properties file for autocomplete support. ```properties helloWorld.desc=Returns a friendly greeting helloWorld.returns=The string "Hi There!" ``` -------------------------------- ### Create PI Tag in OSIsoft PI Asset Framework Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Creates a new tag in OSIsoft PI Asset Framework for historical data storage. Uses a default path for the tag. ```python # Create PI tag from Ignition tag result = PIIntegration.AF.createPITag("[default]PI Integration/Field/Site-A/Sensor-01") ``` -------------------------------- ### Replace Perspective Prints in Device Creation Flow Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Several `system.perspective.print()` calls are used for logging within the device creation flow. These should be replaced with gateway logger calls for better observability in production environments. ```python system.perspective.print("pre-create device") system.perspective.print("pre-create tag") system.perspective.print("post-create tag {0}".format(tagPathSaveResult)) system.perspective.print("post-create device") system.perspective.print(image) ``` ```python _log = system.util.getLogger("device.createDevice") _log.info("Creating device: " + devEUI) _log.debug("Tag path save result: " + str(tagPathSaveResult)) ``` -------------------------------- ### Handle PI Template Creation Errors Gracefully Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Directly calling `createPITemplate.createInstance()` within the device creation flow can cause device creation to fail if PI is unavailable. This snippet shows how to handle potential exceptions during PI provisioning as a best-effort post-creation step. ```python # After confirming device + tag creation succeeded: try: createPITemplate.createInstance(fullTagPath, name) except Exception as e: _log.warn("PI template creation failed for {0}: {1}".format(devEUI, str(e))) message += "; PI template not created" return utils.resultParser.createResults(True, message) ``` -------------------------------- ### List Devices Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Retrieves all registered devices for a tenant or a specific device by DevEUI. ```APIDOC ## GET /api/device/get/listDevices ### Description Retrieves all registered devices for a tenant. ### Method GET ### Endpoint /api/device/get/listDevices ### Parameters #### Query Parameters - **tenantID** (integer) - Required - The ID of the tenant to retrieve devices for. ### Response #### Success Response (200) - Returns an array of device objects, each containing metadata including network credentials and tag assignments. - **devEUI** (string) - Device EUI - **name** (string) - Device display name - **deviceModelID** (integer) - Device profile/model ID - ... (other device metadata) #### Response Example ```json [ {"devEUI": "0004A30B001C1234", "name": "Sensor-01", "deviceModelID": 1, ...}, {"devEUI": "0004A30B001C5678", "name": "Sensor-02", "deviceModelID": 2, ...} ] ``` ## GET /api/device/get/getDevice ### Description Retrieves a specific device's information by its DevEUI. ### Method GET ### Endpoint /api/device/get/getDevice ### Parameters #### Query Parameters - **devEUI** (string) - Required - The 16-character Device EUI of the device to retrieve. ### Response #### Success Response (200) - Returns a single device object with detailed metadata. - **devEUI** (string) - Device EUI - **name** (string) - Device display name - **metaData** (object) - Custom metadata associated with the device - ... (other device details) #### Response Example ```json { "devEUI": "0004A30B001C1234", "name": "Sensor-01", "metaData": { ... }, ... } ``` ``` -------------------------------- ### Use system.util.getLogger for Gateway Logging Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Use system.util.getLogger() for gateway-scope scripts to ensure logs appear in the gateway logs. system.perspective.print() is a no-op in gateway scope. ```python logger = system.util.getLogger("SiteSync-MyModule") logger.info("message") # appears in gateway log logger.error("message") # appears in gateway log at ERROR level ``` -------------------------------- ### Replace print with logger in devices/code.py Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Use system.util.getLogger for gateway-scope logging instead of system.perspective.print, which is a no-op in that context. ```python print "Found unactivated node" ``` ```python logger = system.util.getLogger("PISync-Devices") logger.debug(str(result['value'].value)) logger.debug("Found unactivated node") ``` -------------------------------- ### device.bulkUpload.uploadLine Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Uploads a single device configuration from a spreadsheet row to the system. ```APIDOC ## POST device.bulkUpload.uploadLine ### Description Processes a single device record and creates it within the system. ### Parameters #### Request Body - **row** (object) - Required - Dictionary containing device details (dev_eui, join_eui, app_key, deviceType, tagPath, name, description, serialNumber, installDate) - **deviceProfiles** (list) - Required - List of available device profiles - **tenantID** (int) - Required - ID of the tenant - **tagProvider** (string) - Required - Ignition tag provider name ### Response #### Success Response (200) - **message** (string) - Status message - **status** (string) - Result status (SUCCESS/error) - **deviceName** (string) - Name of the created device - **devEUI** (string) - EUI of the created device ``` -------------------------------- ### Compare SiteSync Core and Enterprise Management Scripts Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Use this command to compare Python script directories between SiteSync Core and SiteSYnc EnterpriseManagementScripts to identify differences. Note that EnterpriseManagementScripts is considered obsolete. ```bash diff -rq SiteSyncCore_2026-04-17_1622/ignition/script-python \ "SiteSYnc EnterpriseManagementScripts/script-python" ``` -------------------------------- ### Compare SiteSync Core and FieldApp Python Scripts Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Use this command to compare Python script directories between SiteSync Core and FieldApp to identify differences and potential improvements. ```bash diff -rq SiteSyncCore_2026-04-17_1622/ignition/script-python \ SiteSync-FieldApp_Improvements_2026-04-11_1548/ignition/script-python ``` -------------------------------- ### Restore Enterprise addTagToPi() implementation in Core Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md The Enterprise version correctly returns PI adapter results by browsing tags, formatting items, and updating data selection. The Core version replaced this with a stub that unconditionally returns False, causing a functional regression. ```python def addTagToPi(tagPath, componentID, PIAddress): items = getAttributesForTag(tagPath) existingConfig = getCurrentDataSelection(componentID, PIAddress) piTags = formatDataSelectionItem(tagPath, items, existingConfig) return updateDataSelection(piTags, componentID, PIAddress) ``` -------------------------------- ### Externalize configuration in addDevices/code.py Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Avoid hardcoding internal hostnames by reading configuration values from tags at runtime. ```python # addDevices/code.py:5–6 PIAddress = "https://pgwgen002923.mgroupnet.com:5590/api/v1/configuration" componentID = "MQTT1" ``` ```python def _getPIAddress(): return system.tag.readBlocking(["[default]Config/PIGatewayURL"])[0].value def _getComponentID(): return system.tag.readBlocking(["[default]Config/PIComponentID"])[0].value ``` -------------------------------- ### Create Ignition Tags for Device Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Creates Ignition tags for a device in a specified tag provider and path. Requires device EUI, provider name, path, device name, and tenant ID. ```python # Create tags for device result = device.tagOperations.saveTagPathForDevice( devEUI="0004A30B001C1234", provider="default", path="Field/Site-A", name="PressureSensor-01", tenantID=1 ) # Returns: {"messageType": "SUCCESS", "message": "Tag created at [default]Field/Site-A/PressureSensor-01"} ``` -------------------------------- ### Use Gateway Logger for Debugging Source: https://github.com/bcs120280/sitesync_optimization/blob/main/PISYNC_CodeReview.txt Use system.util.getLogger for gateway logging to provide actionable diagnostic information without interfering with non-Perspective contexts or producing noise in wrapper logs. ```python log = system.util.getLogger("PISync.findDevices") log.debug("device: %s, sentToPI: %s" % (d['fullTagPath'], activated[0].value)) ``` ```python # remove or: log.debug("activation value: %s" % result['value'].value) ``` ```python log.debug("Found unactivated node: %s" % str(result['fullPath'])) ``` -------------------------------- ### Create LoRaWAN Device Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Creates a new LoRaWAN device in SiteSync with full network credentials and metadata. Ensure all required parameters like devEUI, appEUI, and appKey are correctly formatted. ```python # Create a new LoRaWAN device with full credentials result = device.createDevice.saveDevice( devEUI="0004A30B001C1234", # 16-character Device EUI appEUI="0000000000000001", # 16-character Application/Join EUI appKey="00112233445566778899AABBCCDDEEFF", # 32-character Application Key name="PressureSensor-01", # Device display name serialNumber="SN-2026-001", # Serial number modelID=1, # Device profile/model ID lat=29.7604, # Installation latitude lon=-95.3698, # Installation longitude description="Wellhead pressure monitor", provider="default", # Tag provider name tagPath="Field/Site-A", # Base tag path for device tags image="base64_encoded_image", # Optional device image user="installer@company.com", # User creating device appID=0, # Network server application ID tenantID=1 # Multi-tenant organization ID ) # Returns: {"messageType": "SUCCESS", "message": "Device created successfully", "devEUI": "0004A30B001C1234"} ``` -------------------------------- ### Run full test suite Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Executes the pure test runner and asserts that no failures occurred. ```python # In Ignition Script Console results = tests.runner.runner.runPureOnly() totals = results['totals'] assert totals['failed'] == 0, "{0} test failures: {1}".format( totals['failed'], [r['name'] + ': ' + r['message'] for r in results['results'] if r['status'] == 'FAIL'] ) print("All pure tests: PASS ({0} tests)".format(totals['tests'])) ``` -------------------------------- ### Configure MQTT Broker Connection Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Configures MQTT broker connection settings for real-time data ingestion. Requires broker URL, username, password, tenant ID, connection name, and TLS settings. ```python # Configure MQTT broker connection result = connections.mqtt.saveMqttSettings( url="mqtt.company.com", un="sitesync", pw="secure_password", tenantID=1, connectionName="Production-MQTT", tlsVerification=True, protocol="tcp", brokerID=1, port="8883", auth=True ) # Returns: {"messageType": "SUCCESS", "message": "MQTT settings updated"} ``` -------------------------------- ### Replace `system.perspective.print` with `_log.debug` in device/updateDevice Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Replaces `system.perspective.print` calls with `_log.debug` for effective logging in gateway scope and uses `_log.error` for exception handling. ```python def updateDevice(device): try: system.perspective.print("sending device for update:") system.perspective.print(json.dumps(device)) ... except Exception as e: system.perspective.print("Error updating device: " + str(e)) return "Error updating device: " + str(e) ``` ```python _log = system.util.getLogger("device.updateDevice") def updateDevice(device): try: _log.debug("sending device for update: " + json.dumps(device)) ... except Exception as e: _log.error("Error updating device: " + str(e)) return utils.resultParser.createResults(False, "Error updating device: " + str(e)) ``` -------------------------------- ### Replace Perspective Print with Logger Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Use system.util.getLogger instead of system.perspective.print to ensure error messages are captured in the gateway logs. ```python except Exception as e: system.perspective.print(e) ``` ```python except Exception as e: system.util.getLogger("utils.sitehandler").error("Error listing tenants: " + str(e)) ``` -------------------------------- ### Copy Existing Device Model Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Copies an existing device model to a new model with a specified name. Requires the original model ID. ```python # Copy existing model copied = decoders.model.copyModel(modelID=1, modelName="TEPressure-v2") ``` -------------------------------- ### Use system.util.getLogger for Debug Logging in Gateway Scope Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW.md Replaces `system.perspective.print()` with `system.util.getLogger().debug()` for effective logging in gateway-scope scripts. This ensures debug messages are visible and useful for diagnosing sync problems. ```python logger = system.util.getLogger("PISync-Devices") logger.debug(str(result['value'].value)) logger.debug("Found unactivated node") ``` ```python system.perspective.print(d['fullTagPath']) system.perspective.print(activated) system.perspective.print(activatedToday) system.perspective.print(activatedToday <= 1) ``` -------------------------------- ### Replace bare print statements with gateway loggers Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Updates Python 2 style print statements to use system.util.getLogger for production-compatible logging in gateway scope. ```python print("Tag creation result:", results) ... print("Error creating tag:", e) ``` ```python system.util.getLogger("device.pidGenerator").debug("Tag creation result: " + str(results)) ... system.util.getLogger("device.pidGenerator").error("Error creating tag: " + str(e)) ``` -------------------------------- ### Correct UDT Instance Path Derivation Source: https://github.com/bcs120280/sitesync_optimization/blob/main/PISYNC_CodeReview.txt Derive the target folder path dynamically from the source tag path instead of hardcoding to the root. ```python 3 def createInstance(tagPath, tagName): 4 limitedModel = system.tag.readBlocking(["{0}/metaData/sparkplug/template".format(tagPath)])[0].value 5 if limitedModel != None: 6 baseTagPath = "[default]PI Integration" # ← always root, no subfolder ``` ```python 3 def createInstance(tagPath, tagName): 4 limitedModel = system.tag.readBlocking(["{0}/metaData/sparkplug/template".format(tagPath)])[0].value 5 if limitedModel is not None: 6 baseTagPath = "[default]PI Integration/{0}".format( 7 tagPath.replace(tagName, '').replace('[default]', '').strip('/') 8 ) ``` -------------------------------- ### Audit gateway modules for system.perspective.print Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Lists gateway modules to be audited for the presence of system.perspective.print. ```python # Run in Script Console — should return empty list after all fixes applied import re import os GATEWAY_MODULES = [ "PIIntegration/AF/code.py", "PIIntegration/adapter/code.py", "PIIntegration/status/code.py", "PIIntegration/utils/code.py", "device/updateDevice/code.py", "device/createDevice/code.py", "utils/resultParser/code.py", "utils/sitehandler/code.py", ] issues = [] for mod in GATEWAY_MODULES: # Check module source via inspect or file read pass # implementation depends on Ignition version print("perspective.print audit: review above modules manually") ``` -------------------------------- ### Verify PIIntegration settings Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Validates that PIIntegration settings are correctly returned as a dictionary. ```python settings = PIIntegration.settings.getSettings() assert isinstance(settings, dict), "FAIL: expected dict, got " + str(type(settings)) print("PIIntegration.settings.getSettings null guard: PASS") ``` -------------------------------- ### Implement or raise NotImplementedError for processSystemUpload Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW.md The processSystemUpload function in utils/systemProvisioning is an empty stub, causing silent failures. This recommended fix suggests either implementing the function or raising a NotImplementedError. ```python def processSystemUpload(json): ``` ```python def processSystemUpload(jsonData): raise NotImplementedError("processSystemUpload is not yet implemented") ``` -------------------------------- ### Subscribe to MQTT Topics for Data Collection Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Subscribes to specific MQTT topics for data collection. Requires the topic pattern and broker ID. Returns a success message upon saving the subscription. ```python # Subscribe to device data topic result = connections.mqtt.saveMqttTopics( topic="application/+/device/+/event/up", brokerID=1 ) # Returns: {"messageType": "SUCCESS", "message": "Topic subscription saved"} ``` -------------------------------- ### Retrieve Sensor Data by Product Type Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Fetch sensor data for dashboard display, filtering by sensor type or retrieving all devices. Use 'Simple' or 'Full' for view type. ```python # Get all pressure sensors with simple view devices = getSensors.getDevicesProductType("PRESSURE", "Simple") # Returns: [ # {"Name": "Sensor-01", "DevEUI": "0004A30B001C1234", "pressure": "40.0", "temperature": "25.5", # "Last Reported": "2 hours ago", "Battery": "3.6", "RSSI": "-95"}, # , ... # ] ``` ```python # Get all devices regardless of type all_devices = getSensors.getDevicesProductType("ALL", "Simple") ``` ```python # Get devices by model name model_devices = getSensors.getDevicesbyModel("TEPressure", "Full") ``` -------------------------------- ### Verify PI URL configuration Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Checks that the PI address is not hardcoded in the addDevices function. ```python # After fix, PIAddress should come from configuration, not be a literal import inspect src = inspect.getsource(addDevices) assert "pgwgen002923" not in src, "FAIL: hardcoded PI URL still present" print("addDevices hardcoded URL: PASS") ``` -------------------------------- ### Save PI Web API Settings for Asset Framework Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Configures the connection settings for PI Web API to perform Asset Framework operations. Requires URL, token, and repository path. ```python # Save AF Web API settings af_settings = { "url": "https://piwebapi.company.com/piwebapi", "token": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=", "repository": "\\PISERVER\PIDatabase" } result = PIIntegration.AF.saveAFSettings(af_settings) ``` -------------------------------- ### List Available UDTs Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Lists all available User Defined Types (UDTs) for a given tenant ID. ```python # List available UDTs udts = decoders.udt.listUDTs(tenantID=1) ``` -------------------------------- ### PIIntegration.AF.createPITag / updatePITag Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Methods to create, update, or verify the existence of tags in the OSIsoft PI Asset Framework. ```APIDOC ## PIIntegration.AF.createPITag / updatePITag ### Description Creates or updates tags in OSIsoft PI Asset Framework for historical data storage. ### Parameters #### Request Body - **tagPath** (string) - Required - The full path of the Ignition tag to be mapped to PI. ### Response #### Success Response (200) - **status** (boolean) - Indicates if the operation was successful. - **message** (string) - Status message describing the result. ``` -------------------------------- ### Fix Data Selection Null Handling in addDevices Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Corrects the logic to properly handle 'null' strings returned by the PI adapter. ```python selectedData = system.piAdapter.getDataSelection(componentID, "MQTT1", PIAddress) if selectedData != None: # "null" string passes this check return json.loads(selectedData) # returns Python None, not a dict ``` ```python selectedData = system.piAdapter.getDataSelection(componentID, "MQTT1", PIAddress) if selectedData and selectedData != "null": parsed = json.loads(selectedData) if parsed is None: return [] return parsed return [] ``` -------------------------------- ### Fix JSON null handling in getSettings Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Guards against 'null' string returns from system.piAdapter.getSettings to prevent TypeError when accessing keys on None. ```python def getSettings(): a = system.piAdapter.getSettings("generic") return json.loads(a) # json.loads("null") == None if not configured ``` ```python def getSettings(): a = system.piAdapter.getSettings("generic") if a is None or a == "null": return {} return json.loads(a) ``` -------------------------------- ### Correct system.tag.browse usage Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Replaces incorrect recursive filter in system.tag.browse with system.tag.browseConfiguration. ```python tags = system.tag.browse(rootTagPath, filter={"recursive":True}) tags = system.tag.browse(monitoredPath, filter={"recursive":True, "tagType":"UdtInstance"}) ``` ```python tags = system.tag.browseConfiguration(rootTagPath, recursive=True) # For tagType filter, post-filter results: tags = [t for t in system.tag.browseConfiguration(monitoredPath, recursive=True) if t.get('tagType') == 'UdtInstance'] ``` -------------------------------- ### Verify PIIntegration status implementation Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Checks that the adapterAPIPingStatus function is not a stub by inspecting its source code. ```python # After removing duplicate stubs, real implementation should be reachable # This verifies the function body is not a stub import inspect src = inspect.getsource(PIIntegration.status.adapterAPIPingStatus) assert "testConnection" in src, "FAIL: real implementation not found" print("PIIntegration.status: PASS") ``` -------------------------------- ### Replace Perspective Debugging with Gateway Logging Source: https://github.com/bcs120280/sitesync_optimization/blob/main/PISYNC_CodeReview.txt Remove session-scoped perspective print calls to prevent errors in non-Perspective contexts and use system.util.getLogger for robust logging. ```python 30 system.perspective.print(createReult) ``` ```python # Remove the line entirely, or replace with a gateway logger: 30 system.util.getLogger("PISync.createPITemplate").debug("configure result: %s" % createReult) ``` -------------------------------- ### List All Payload Decoders Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Lists all available payload decoders for a given tenant ID, including their ID, name, and type. ```python # List all decoders decoders_list = decoders.decoder.listDecoders(tenantID=1) ``` -------------------------------- ### Refactored Device Model Filter Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW.md A more robust and documented approach to filtering placeholder device profiles. ```python EXCLUDED_SUFFIXES = ('-NA',) # placeholder profiles not shown in dropdowns for device in deviceModels: model = device["model_name"] if not any(model.endswith(s) for s in EXCLUDED_SUFFIXES): ``` -------------------------------- ### Improve Error Logging in createPITemplate Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Replaces silent perspective printing with proper logger usage for gateway-side error visibility. ```python except Exception as e: system.perspective.print("Error " + str(e)) # silent no-op in gateway return None ``` ```python except Exception as e: logger = system.util.getLogger("SiteSync-PITemplate") logger.error("createInstance failed for {0}: {1}".format(tagPath, str(e))) return None ``` -------------------------------- ### Replace Bare Print Statements with Logging Source: https://github.com/bcs120280/sitesync_optimization/blob/main/PISYNC_CodeReview.txt Replace bare print statements, which write to stdout and the wrapper log, with gateway logger calls for actionable operational information and cleaner diagnostics. ```python print result['value'].value ``` ```python print "Found unactivated node" ``` -------------------------------- ### Retrieve Remote Log Entries Source: https://github.com/bcs120280/sitesync_optimization/blob/main/ignition-sdk_examples-main/gateway-network-function/readme.md Uses system.example.getRemoteLogEntries to fetch log data from specified servers within a defined time range. ```python import datetime servers = ["agent"] startTime = datetime.datetime.now() - datetime.timedelta(hours=3) map = system.example.getRemoteLogEntries(servers, startTime) serverLogs = map["agent"] for row in range(serverLogs.rowCount): printable = { 'level': serverLogs.getValueAt(row, "level"), 'name': serverLogs.getValueAt(row, "name"), 'timestamp': serverLogs.getValueAt(row, "timestamp"), 'message': serverLogs.getValueAt(row, "message") } baseStr = '%(level)s [%(name)s] [%(timestamp)s]: %(message)s' print baseStr % printable ``` -------------------------------- ### Replace gateway-scope perspective print Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Perspective print calls are no-ops in gateway scope. Use a logger instead to capture debug information. ```python system.perspective.print(j) ``` ```python system.util.getLogger("PIIntegration.status").debug(str(j)) ``` -------------------------------- ### Bulk Upload Single Device Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Uploads a single device's data from a dictionary representing a spreadsheet row. Ensure the 'dev_eui' is 16 characters long for successful processing. ```python row = { "dev_eui": "0004A30B001C1234", "join_eui": "0000000000000001", "app_key": "00112233445566778899AABBCCDDEEFF", "deviceType": "TEPressure", "tagPath": "Field/Site-A", "name": "Pressure-01", "description": "Wellhead sensor", "serialNumber": "SN-001", "installDate": "2026-04-17" # Custom metadata field } deviceProfiles = decoders.model.listDeviceProfiles(tenantID=1) tagProvider = "default" status = device.bulkUpload.uploadLine(row, deviceProfiles, tenantID=1, tagProvider=tagProvider) # Returns: {"message": "Device created", "status": "SUCCESS", "deviceName": "Pressure-01", "devEUI": "0004A30B001C1234"} # Or: {"message": "DevEUI is not 16 characters, upload not attempted", "status": "error", ...} ``` -------------------------------- ### Implement processSystemUpload stub Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Replaces an empty function body with a NotImplementedError to prevent silent failures during system uploads. ```python def processSystemUpload(json): ``` ```python def processSystemUpload(jsonData): raise NotImplementedError("processSystemUpload is not yet implemented") ``` -------------------------------- ### Save and Send Downlink Commands Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Use saveDownlink to create or update downlink commands and sendDownlink to execute them. Ensure correct device and tenant IDs are provided. ```python # Create a downlink command result = decoders.downlinks.saveDownlink( downlinkID=0, # 0 for new, existing ID for update deviceProfileID=1, hexCommand="01FF", port=2, description="Reset device", name="Factory Reset" ) # Returns: {"messageType": "SUCCESS", "id": 1} ``` ```python # List downlinks for device profile downlinks = decoders.downlinks.listDownlinks(deviceProfileID=1) # Returns: [{"id": 1, "name": "Factory Reset", "hexCommand": "01FF", "port": 2}, ...] ``` ```python # Execute downlink command send_result = decoders.downlinks.sendDownlink( hexCode="01FF", port=2, devEUI="0004A30B001C1234", tenantID=1 ) # Returns: {"messageType": "SUCCESS", "message": "Downlink queued"} ``` ```python # Process downlink with variable input processed_hex = decoders.downlinks.processAnyInputs("01{0}FF", "AA") # Returns: "01AAFF" ``` -------------------------------- ### Verify PIIntegration path formatting Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Ensures that pathFormatter returns paths using forward slashes instead of backslashes. ```python # pathFormatter should use forward slashes result = PIIntegration.AF.pathFormatter("MyRepo", "path.$.to.tag", "prefix", "sourceFolder") assert "\\" not in result, "FAIL: backslash found in path: " + result assert "/" in result, "FAIL: no forward slash in path: " + result print("PIIntegration.AF.pathFormatter: PASS") ``` -------------------------------- ### Update PI Integration Settings Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Updates the PI integration settings. Requires a dictionary containing new settings like prefix and source folder. ```python # Update PI settings new_settings = {"prefix": "SITESYNC", "sourceFolder": "PI Integration"} PIIntegration.settings.updateSettings(new_settings) ``` -------------------------------- ### Fix createInstance tag path separator Source: https://github.com/bcs120280/sitesync_optimization/blob/main/PISYNC_CodeReview.txt Correct the tag path separator from a dot to a slash to ensure the activation flag is written to the child tag. ```python 96 system.tag.writeBlocking([tagPath + ".activated"], [True]) ``` ```python 96 system.tag.writeBlocking([tagPath + "/activated"], [True]) ``` -------------------------------- ### Replace perspective.print with gateway logger in pidGenerator Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Use system.util.getLogger for logging in gateway scope to prevent silent error swallowing. ```python system.perspective.print("Warning: Failed to write loop number to tag") system.perspective.print("Error accessing loop number tag: {0}".format(str(e))) ``` -------------------------------- ### Utility Functions Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Helper functions for result parsing and UI component formatting. ```APIDOC ## POST utils.resultParser.createResults ### Description Creates a standardized result object for API responses. ### Parameters #### Request Body - **success** (boolean) - Required - Operation status - **message** (string) - Required - Result message ``` -------------------------------- ### Correct recursive tag browsing Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Use system.tag.browseConfiguration for recursive searches as the recursive filter in system.tag.browse may be ignored. ```python results = system.tag.browse(path = searchDirectory, filter = {'name':activationName, "recursive":True}) ``` -------------------------------- ### Configure LoRaWAN Network Server Settings Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Configures LoRaWAN network server connection settings for various providers like ChirpStack, TTN, Loriot, and Actility. Requires tenant ID, network type, and specific server credentials/tokens. ```python # Configure ChirpStack network server result = connections.networkserver.saveNetworkSettings( tenantID=1, networkType="CHIRPSTACK", joinServerUN="", joinServerPW="", apiToken="eyJhbGciOiJIUzI1NiIs...", serverURL="https://chirpstack.company.com", nwsID=1, appID="application-id-123", defaultProfileID="device-profile-001" ) # Returns: {"messageType": "SUCCESS", "message": "Network server settings saved"} ``` -------------------------------- ### Restore dynamic tenant ID retrieval in getDefaultApp() Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md The original implementation of `getDefaultApp` was hardcoded to return `3`. This fix restores the dynamic retrieval of the tenant ID by querying `system.sitesync.getPrimaryNetworkServerAccount()`. Use this when multi-tenant isolation is required. ```python def getDefaultApp(): return 3 # j = system.sitesync.getPrimaryNetworkServerAccount() # if j != "null": # app = json.loads(j) # return app['id'] # else: # return -1 ``` ```python def getDefaultApp(): j = system.sitesync.getPrimaryNetworkServerAccount() if j != None and j != "null": app = json.loads(j) return app['id'] return -1 ``` -------------------------------- ### Fix Typo and Gateway Scope Logging in createPITemplate Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Corrects a typo in a variable name and replaces `system.perspective.print` with `system.util.getLogger().error` for proper exception logging in gateway scope. ```python createReult = system.tag.configure(baseTagPath, [tag], collisionPolicy) ... except Exception as e: system.perspective.print("Error " + str(e)) ``` ```python create_result = system.tag.configure(baseTagPath, [tag], collisionPolicy) ... except Exception as e: system.util.getLogger("createPITemplate").error("Error in createInstance: " + str(e)) ``` -------------------------------- ### Test Network Server API Connectivity Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Tests the connectivity to the network server API. Requires tenant ID as input. ```python test_result = connections.networkserver.testAPI(tenantID=1) ``` -------------------------------- ### Format Dropdown Options for Perspective Components Source: https://context7.com/bcs120280/sitesync_optimization/llms.txt Use formatDropdownOption to create properly formatted options for use in Perspective dropdown components, specifying both the label and value. ```python # Format dropdown option option = utils.dropdowns.formatDropdownOption("Pressure Sensor", 1) # Returns: {"label": "Pressure Sensor", "value": 1} ``` -------------------------------- ### Correct and Defective replaceNull Implementations Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW.md Compares the defective implementation where both branches return 0 with the corrected version that returns the original value. ```python def replaceNull(value): if value == None: return 0 else: return 0 # ← both branches return 0 ``` ```python def replaceNull(value): if value is None: return 0 return value ``` -------------------------------- ### Use forward slash for PI path formatting Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md This code snippet demonstrates incorrect path formatting using a backslash as a separator, which is problematic in Linux environments for the PI Web API. The recommended fix replaces the backslash with a forward slash to ensure correct path construction. ```python endpoint = "{0}\\{1}/{2}".format(repo, prefix, ss) ``` ```python endpoint = "{0}/{1}/{2}".format(repo, prefix, ss) ``` -------------------------------- ### Recommended Fix for PI Tag Path Formatting Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW.md Apply the SiteSyncCore fix to prevent double-slashes in PI tag paths by directly concatenating path components. ```python results = addDevices.addTagToPi("{0}{1}".format(baseTagPath, tagName), ...) ``` -------------------------------- ### Correct Usage of system.tag.readBlocking Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Ensures `system.tag.readBlocking()` receives a list of tag paths, as expected. Passing a string directly can lead to unexpected behavior. ```python instance = system.tag.readBlocking(udtInstancePath)[0] ``` ```python instance = system.tag.readBlocking([udtInstancePath])[0] ``` -------------------------------- ### Recommended Site Roles Update Implementation Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW.md A proposed fix for the role update function that reconciles the API payload shape and handles empty role lists. ```python def updateSiteRoles(siteID, rolesJSON): r = json.dumps(rolesJSON) # or json.dumps({"roles": rolesJSON}) — verify with API result = system.sitesync.updateTenantRoles(r, int(siteID)) return json.loads(result) if result else utils.resultParser.createResults(True, "Roles updated") ``` -------------------------------- ### Replace system.perspective.print with logging in exception handler Source: https://github.com/bcs120280/sitesync_optimization/blob/main/CODE_REVIEW (1).md Replaces `system.perspective.print` with `system.util.getLogger().error` for more robust error logging within exception handlers. This ensures errors are logged appropriately on the gateway instead of being silently ignored. ```python except Exception as e: system.perspective.print("Error determining status: " + str(e)) return False ``` ```python except Exception as e: system.util.getLogger("utils.resultParser").error("Error determining status: " + str(e)) return False ```