Compare commits

..

12 Commits

Author SHA1 Message Date
Quentin WEPHRE
fcc8c811cc more on event topic for less cloud usage 2026-03-26 08:48:53 +01:00
Quentin WEPHRE
3dd5974d70 api call through cloud for i-sight moxa 2026-03-26 08:48:15 +01:00
Quentin WEPHRE
007f30a786 added subsite tag 2026-03-26 08:47:15 +01:00
Quentin WEPHRE
b8c18689bf added on event topics 2026-03-26 08:46:57 +01:00
Quentin WEPHRE
7314eef7a9 update git ignore 2026-03-26 08:46:42 +01:00
Quentin WEPHRE
abd00f9e04 multiple evolutions 2025-12-04 08:11:00 +01:00
Quentin WEPHRE
e9715dc239 correction on moulinette 2025-11-19 12:27:09 +01:00
Quentin WEPHRE
3003ee530f Merge branch 'main' of github.com:TotalEnergiesCode/ess-moxa-configuration-tools 2025-11-19 12:26:18 +01:00
Quentin WEPHRE
3bebce0f4b danish moxa update 2025-11-19 12:26:12 +01:00
Quentin WEPHRE
1c0a5e2697 Merge remote-tracking branch 'refs/remotes/origin/main' 2025-10-17 16:54:10 +02:00
Quentin WEPHRE
520dc2f371 Merge branch 'main' of github.com:TotalEnergiesCode/ess-moxa-configuration-tools 2025-10-14 15:12:31 +02:00
Quentin WEPHRE
e3e3902891 port-dependant (nat) batch for cube inventory 2025-10-14 15:12:12 +02:00
17 changed files with 1523 additions and 115 deletions

3
.gitignore vendored
View File

@@ -18,3 +18,6 @@ Python/dist/*
*.spec
dist/*
*.7z
*.tar
*.tar.gz
*.zip

View File

@@ -40,7 +40,7 @@ allowed_name_characters.append('.')
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', filename='data_config_debug.log', filemode='w', level=logging.DEBUG, datefmt='%Y%m%d%H%M%S')
dir_name = 'I-Sight_Generated_Files'
input_datamodel = 'DATAMODEL_1.0.6_LIBERTY.xlsx'
input_datamodel = 'DATAMODEL_Ruakaka_QWE1.xlsx'
shell_script_name = dir_name + '/I-Sight_Configuration_'
global_shell_script_name = dir_name + '/I-Sight_Global_Configuration.sh'
@@ -88,7 +88,7 @@ def excel_parser(sheet_det, slave_no, slave_device, allowed_name_characters, dsh
logging.debug("Starting registration of Modbus commands for " + str(assets_name[slave_no]) + ".")
for index, row in filtered_df_prop.iterrows():
step2_data = {}
logging.debug("Registering command " + row['metric_name'] + "...")
logging.debug("Registering command " + row['metric_name'] + " on address " + str(row['modbus_address']))
if allowed_data_sizes[allowed_data_types.index(row['type'])] != 1 and int(row['modbus_quantity']) % allowed_data_sizes[allowed_data_types.index(row['type'])] != 0:
logging.debug("Wrong quantity (" + str(int(row['modbus_quantity'])) + ") for data type " + str(row['type']) + ", using default size " + str(allowed_data_sizes[allowed_data_types.index(row['type'])]) + ".")
step2_data["readQuantity"] = allowed_data_sizes[allowed_data_types.index(row['type'])]
@@ -104,10 +104,11 @@ def excel_parser(sheet_det, slave_no, slave_device, allowed_name_characters, dsh
else:
logging.debug("Poll interval undefined, using 1000ms as default.")
step2_data["pollInterval"] = 1000
if row['endian_swap']:
if not math.isnan(row['endian_swap']):
logging.debug(f"Endian Swap: {row['endian_swap']} {row['metric_name']}")
step2_data["swap"] = int(row['endian_swap'])
else:
logging.debug("Endian Swap undefined, not using swap as default.")
logging.debug(f"Endian Swap undefined, not using swap as default. {row['metric_name']}")
step2_data["swap"] = 0
step2_data["dataType"] = row['type']
if not math.isnan(row['scaling_factor']):
@@ -207,7 +208,7 @@ def jq_filter(current_device, dsh, dsh_global, row_device):
jq_data["enable"] = False
jq_data["properties"] = [{"key": "deviceType", "value": "AC_GATEWAY"}, {"key": "cdid", "value": current_device}]
jq_data["outputTopic"] = filter
jq_data["sendOutThreshold"] = {"mode": "bySize", "size": int(128000), "time": int(30), "sizeIdleTimer": {"enable": True, "time": int(30)}}
jq_data["sendOutThreshold"] = {"mode": "bySize", "size": int(128000), "time": int(30), "sizeIdleTimer": {"enable": True, "time": int(5)}}
jq_data["minPublishInterval"] = int(0)
jq_data["samplingMode"] = "allValues"
jq_data["customSamplingRate"] = False
@@ -245,6 +246,80 @@ def jq_filter(current_device, dsh, dsh_global, row_device):
dsh_global.write("echo -e \"\\n\" >> global_config.log\n\n")
dsh_global.write("echo \"Finished work on " + str(row_device['device_name']) + "\"")
if filter == "generic_metrics":
logging.debug("Creating OnEvent telemetry topic.")
jq_data = {}
jq_data["enable"] = False
jq_data["properties"] = [{"key": "deviceType", "value": "AC_GATEWAY"}, {"key": "cdid", "value": current_device}]
jq_data["outputTopic"] = "PCS_OnEvent"
jq_data["sendOutThreshold"] = {"mode": "immediately", "size": int(4096), "time": int(60), "sizeIdleTimer": {"enable": True, "time": int(60)}}
jq_data["minPublishInterval"] = int(0)
jq_data["samplingMode"] = "allChangedValues"
jq_data["customSamplingRate"] = False
jq_data["pollingInterval"] = int(0)
jq_data["onChange"] = True
final_filter = row["jq_filter"].replace("***device_name***", current_device)
cmd_list = {}
jq_data["format"] = final_filter
jq_data["tags"]= {"modbus_tcp_master": cmd_list}
json_object = json.dumps(jq_data)
dsh.write("# [STEP 4] Creating " + filter + " OnEvent Azure telemetry topic\n\n")
dsh.write(
inspect.cleandoc(
"""sudo curl -X POST https://127.0.0.1:8443/api/v1/azure-iotedge/messages \\
-H "Content-Type: application/json" \\
-H "mx-api-token:$(sudo cat /var/thingspro/data/mx-api-token)" \\
-d """) + "'" + str(json_object) +"'" + """ -k | jq
\n"""
)
dsh.write('printf "\\n \\n" >> data_shell_script.log\n')
dsh.write("\n\n")
dsh_global.write("### Creating OnEvent Azure messages " + "" + " for " + str(row_device['device_name']) + "\n")
dsh_global.write("curl -s -X POST -k https://" + row_device['device_ip_address_http'] + ":8443/api/v1/azure-iotedge/messages \\\n")
dsh_global.write("\t-H \"Content-Type: application/json\" \\\n")
dsh_global.write("\t-H \"mx-api-token: ${token}\" \\\n")
dsh_global.write("\t-d '" + str(json_object) + "' >> global_config.log\n\n")
dsh_global.write("echo -e \"\\n\" >> global_config.log\n\n")
dsh_global.write("echo \"Finished work on " + str(row_device['device_name']) + "\"")
logging.debug("Creating 12H telemetry topic.")
jq_data = {}
jq_data["enable"] = False
jq_data["properties"] = [{"key": "deviceType", "value": "AC_GATEWAY"}, {"key": "cdid", "value": current_device}]
jq_data["outputTopic"] = "PCS_12H"
jq_data["sendOutThreshold"] = {"mode": "byTime", "size": int(4096), "time": int(43200), "sizeIdleTimer": {"enable": True, "time": int(60)}}
jq_data["minPublishInterval"] = int(0)
jq_data["samplingMode"] = "latestValues"
jq_data["customSamplingRate"] = False
jq_data["pollingInterval"] = int(43200)
jq_data["onChange"] = False
final_filter = row["jq_filter"].replace("***device_name***", current_device)
cmd_list = {}
jq_data["format"] = final_filter
jq_data["tags"]= {"modbus_tcp_master": cmd_list}
json_object = json.dumps(jq_data)
dsh.write("# [STEP 4] Creating " + filter + " 12H Azure telemetry topic\n\n")
dsh.write(
inspect.cleandoc(
"""sudo curl -X POST https://127.0.0.1:8443/api/v1/azure-iotedge/messages \\
-H "Content-Type: application/json" \\
-H "mx-api-token:$(sudo cat /var/thingspro/data/mx-api-token)" \\
-d """) + "'" + str(json_object) +"'" + """ -k | jq
\n"""
)
dsh.write('printf "\\n \\n" >> data_shell_script.log\n')
dsh.write("\n\n")
dsh_global.write("### Creating 12H Azure messages " + "" + " for " + str(row_device['device_name']) + "\n")
dsh_global.write("curl -s -X POST -k https://" + row_device['device_ip_address_http'] + ":8443/api/v1/azure-iotedge/messages \\\n")
dsh_global.write("\t-H \"Content-Type: application/json\" \\\n")
dsh_global.write("\t-H \"mx-api-token: ${token}\" \\\n")
dsh_global.write("\t-d '" + str(json_object) + "' >> global_config.log\n\n")
dsh_global.write("echo -e \"\\n\" >> global_config.log\n\n")
dsh_global.write("echo \"Finished work on " + str(row_device['device_name']) + "\"")
STATIC_JQ_FILTER_EMISSIONDATE = "(now|todateiso8601)"
def bitfield_jq_filter(current_device):

View File

@@ -15,20 +15,21 @@ if CONNECTION_STRING == "":
print("Provide a connection string for the Iot Hub before running the script!")
exit(13)
SITE_NAME = "LIBERTY"
SITE_NAME = "Bell"
registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING)
query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND tags.site = '" + SITE_NAME + "' AND capabilities.iotEdge = true")
#query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND tags.site = '" + SITE_NAME + "' AND capabilities.iotEdge = true")
query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND tags.site = '" + SITE_NAME + "' AND capabilities.iotEdge = false")
query_result = registry_manager.query_iot_hub(query_spec)
devices = []
for item in query_result.items:
deviceId = str(item.device_id)
site = str(item.tags['site'])
number = int(item.tags['number'])
cloud_version = str(item.tags['version'])
site = str(item.tags.get('site')) if item.tags.get('site') else None
number = int(item.tags.get('number')) if item.tags.get('number') else None
cloud_version = str(item.tags.get('version')) if item.tags.get('version') else None
devices.append([deviceId, site, number, cloud_version])
ordered_devices = sorted(devices, key = lambda x: (x[1], x[2]))

View File

@@ -30,15 +30,20 @@ for twin in query_result.items:
"device_id": twin.device_id,
"number": twin.tags.get("number") if twin.tags else None,
"site": twin.tags.get("site") if twin.tags else None,
"subsite": twin.tags.get("subsite") if twin.tags else None,
"connection_state": twin.connection_state,
"last_activity_time": twin.last_activity_time,
"last_activity_time": twin.last_activity_time
})
df = pd.DataFrame(rows)
df['number'] = pd.to_numeric(df['number'], errors='coerce')
df['number'] = df['number'].astype('Int64')
df_sorted = df.sort_values(by=["site", "number"]).reset_index(drop=True)
print(df_sorted)
for row in df_sorted.itertuples():
if "cube" not in row.device_id:
print(f"\"{row.device_id}\", \"{row.site}\", \"{row.number}\",")
# Compute difference in hours (float)
df_sorted["time_since_last_activity_hours"] = df_sorted["last_activity_time"].apply(
@@ -56,4 +61,4 @@ if "last_activity_time" in df_sorted.columns:
)
df_sorted.to_excel("iot_devices.xlsx", index=False)
df_sorted.to_excel("iot_devices_20260204.xlsx", index=False)

View File

@@ -0,0 +1,64 @@
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.protocol.models import QuerySpecification, Module
from azure.iot.hub.models import CloudToDeviceMethod, CloudToDeviceMethodResult
from dotenv import load_dotenv
from isight_device import iSightDevice
import json
import os
import pandas as pd
load_dotenv()
CONNECTION_STRING = str(os.getenv("CONNECTION_STRING_INOX_PROD"))
if CONNECTION_STRING == "":
print("Provide a connection string for the Iot Hub before running the script!")
exit(13)
registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING)
query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND capabilities.iotEdge = true ")
query_result = registry_manager.query_iot_hub(query_spec)
devices = []
for item in query_result.items:
devices.append(iSightDevice(str(item.device_id), str(item.tags['site']), int(item.tags['number']), str(item.tags['version'])))
devices.sort(key = lambda d: (d.site, d.number))
rows = []
for device in devices:
print(device, end="\t")
current_device_modules = registry_manager.get_modules(device.deviceId)
for module in current_device_modules:
if (module.module_id == "thingspro-agent"):
device.setModule(module)
method_name = "thingspro-api-v1"
payload = {
"method": "GET",
"path": "/device/general"
}
module_id = "thingspro-agent"
try:
direct_method = CloudToDeviceMethod(method_name = method_name, payload = payload)
response = registry_manager.invoke_device_module_method(device_id = device.deviceId, module_id = module_id, direct_method_request = direct_method)
#print(response)
#print(json.dumps(response.payload, indent = 2))
hostname = response.payload['data']['hostName']
serial = response.payload['data']['serialNumber']
version = response.payload['data']['firmwareVersion']
description = response.payload['data']['description']
print(hostname + " " + serial + " " + version + " " + description)
rows.append({
"hostname": hostname,
"site": device.getSite(),
"number": device.getNumber(),
"cloud_version": device.getVersion(),
"device_version": version
})
except Exception as e:
print(f"Error: {e}")
df = pd.DataFrame(rows)
df.to_excel("thingspro-version.xlsx", index=False)

View File

@@ -0,0 +1,55 @@
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.protocol.models import QuerySpecification, Module
from azure.iot.hub.models import CloudToDeviceMethod, CloudToDeviceMethodResult
from dotenv import load_dotenv
from isight_device import iSightDevice
import json
import os
import pandas as pd
load_dotenv()
CONNECTION_STRING = str(os.getenv("CONNECTION_STRING_INOX_PROD"))
if CONNECTION_STRING == "":
print("Provide a connection string for the Iot Hub before running the script!")
exit(13)
registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING)
query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND capabilities.iotEdge = true ")
query_result = registry_manager.query_iot_hub(query_spec)
devices = []
for item in query_result.items:
devices.append(iSightDevice(str(item.device_id), str(item.tags['site']), int(item.tags['number']), str(item.tags['version'])))
devices.sort(key = lambda d: (d.site, d.number))
rows = []
for device in devices:
if device.getDeviceId() == "TBAIB1114211":
print(device, end="\t")
current_device_modules = registry_manager.get_modules(device.deviceId)
for module in current_device_modules:
if (module.module_id == "thingspro-agent"):
device.setModule(module)
method_name = "thingspro-api-v1"
payload = {
"method": "GET",
"path": "/device/time"
}
module_id = "thingspro-agent"
try:
direct_method = CloudToDeviceMethod(method_name = method_name, payload = payload)
response = registry_manager.invoke_device_module_method(device_id = device.deviceId, module_id = module_id, direct_method_request = direct_method)
print(json.dumps(response.payload, indent = 2))
except Exception as e:
print(f"Error: {e}")
else:
continue
print(f"{device.getDeviceId()}")
df = pd.DataFrame(rows)
df.to_excel("thingspro-version.xlsx", index=False)

View File

@@ -0,0 +1,69 @@
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.protocol.models import QuerySpecification, Module
from azure.iot.hub.models import CloudToDeviceMethod, CloudToDeviceMethodResult
from dotenv import load_dotenv
from isight_device import iSightDevice
import json
import os
import pandas as pd
load_dotenv()
CONNECTION_STRING = str(os.getenv("CONNECTION_STRING_INOX_PROD"))
NEW_MOXA_PASSWORD = str(os.getenv("NEW_MOXA_PASSWORD"))
if CONNECTION_STRING == "":
print("Provide a connection string for the Iot Hub before running the script!")
exit(13)
registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING)
query_spec = QuerySpecification(query="SELECT * FROM devices WHERE IS_DEFINED(tags.site) AND capabilities.iotEdge = true ")
query_result = registry_manager.query_iot_hub(query_spec)
devices = []
for item in query_result.items:
devices.append(iSightDevice(str(item.device_id), str(item.tags['site']), int(item.tags['number']), str(item.tags['version'])))
devices.sort(key = lambda d: (d.site, d.number))
rows = []
for device in devices:
current_device_modules = registry_manager.get_modules(device.deviceId)
for module in current_device_modules:
if (module.module_id == "thingspro-agent"):
device.setModule(module)
method_name = "thingspro-api-v1"
payload = {
"method": "GET",
"path": "/users"
}
module_id = "thingspro-agent"
try:
direct_method = CloudToDeviceMethod(method_name = method_name, payload = payload)
response = registry_manager.invoke_device_module_method(device_id = device.deviceId, module_id = module_id, direct_method_request = direct_method)
for i in range(int(response.payload['count'])):
username = response.payload['data'][i]["name"]
userid = int(response.payload['data'][i]["id"])
if username == "admin":
print(f"Found {username} at ID {str(userid)} on device {device.getDeviceId()} from {device.getSite()} ({device.getNumber()})")
try:
payloadpassword = {
"method": "PUT",
"path": f"/users/{str(userid)}/password",
"requestBody": {
"newPassword": f"{NEW_MOXA_PASSWORD}"
}
}
direct_method_password = CloudToDeviceMethod(method_name = method_name, payload = payloadpassword)
response_password = registry_manager.invoke_device_module_method(device_id = device.deviceId, module_id = module_id, direct_method_request = direct_method_password)
except Exception as e:
print(f"Error while changing password on device {device.getDeviceId()} from {device.getSite()} ({device.getNumber()})")
continue
except Exception as e:
print(f"Error while getting users on device {device.getDeviceId()} from {device.getSite()} ({device.getNumber()})")
continue
# df = pd.DataFrame(rows)
# df.to_excel("thingspro-users.xlsx", index=False)

View File

@@ -87,7 +87,7 @@ def set_ssh_status(base_url, token):
else:
raise
def activate_ssh(ip_address):
def activate_ssh(ip_address, silent = False):
# Ensure the URL uses HTTPS
url = ip_address
@@ -104,17 +104,23 @@ def activate_ssh(ip_address):
if not verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
print(f"HTTPS", end=" ", flush=True)
if not silent:
print(f"HTTPS", end=" ", flush=True)
try:
token = authenticate(url)
print(f"", end="", flush=True)
if not silent:
print(f"", end="", flush=True)
except Exception as e:
print(f"", flush=True)
if not silent:
print(f"", flush=True)
raise
print(f"SSH", end=" ", flush=True)
if not silent:
print(f"SSH", end=" ", flush=True)
try:
set_ssh_status(url, token)
print(f"", end="\n", flush=True)
if not silent:
print(f"", end="\n", flush=True)
except Exception as e:
print(f"", flush=True)
if not silent:
print(f"", flush=True)
raise

View File

@@ -0,0 +1,118 @@
import requests
import json
import os
import urllib3
from dotenv import load_dotenv
import time
import sys
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
dotenv_path = resource_path('.env')
load_dotenv(dotenv_path=dotenv_path)
def authenticate(base_url):
"""
Authenticate with the CUBE API using username, password and certificate.
Returns the JWT token if successful.
"""
auth_url = f"{base_url}/api/auth"
ENV_WEB = {
"DEFAULT_CUBE_WEB_ADMIN_USER": os.getenv("DEFAULT_CUBE_WEB_ADMIN_USER"),
"DEFAULT_CUBE_WEB_ADMIN_PASSWORD": os.getenv("DEFAULT_CUBE_WEB_ADMIN_PASSWORD"),
"DEFAULT_CERTIFICATE": os.getenv("DEFAULT_CERTIFICATE")
}
username = ENV_WEB["DEFAULT_CUBE_WEB_ADMIN_USER"]
password = ENV_WEB["DEFAULT_CUBE_WEB_ADMIN_PASSWORD"]
certificate = ENV_WEB["DEFAULT_CERTIFICATE"].encode("utf-8")
auth_params = {
"login": username,
"password": password
}
files = {
"params": (None, json.dumps(auth_params), "application/json"),
"certificate": ("certificate.pem", certificate, "application/octet-stream")
}
try:
response = requests.post(auth_url, files=files, verify=False, timeout=10)
response.raise_for_status() # Raise exception for 4XX/5XX responses
# Extract token from response
auth_data = response.json()
token = auth_data.get("token")
if not token:
raise requests.exceptions.RequestException
print("HTTPS ✅", end = " ", flush=True)
return token
except requests.exceptions.RequestException as e:
print(f"HTTPS ❌", flush=True)
if hasattr(e, 'response') and e.response:
raise Exception(e.response)
else:
raise
def set_ssh_status(base_url, token):
"""
Set SSH status (enable) using the provided JWT token.
"""
ssh_url = f"{base_url}/api/ssh"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
}
# Set new SSH status
payload = { "currentStatus": True }
try:
response = requests.post(ssh_url, headers=headers, json=payload, verify=False, timeout=10)
response.raise_for_status()
print(f"SSH ✅", end = " ", flush=True)
return True
except requests.exceptions.RequestException as e:
print("SSH ❌", flush=True)
if hasattr(e, 'response') and e.response:
raise Exception(e.response)
else:
raise
def activate_ssh(ip_address, port):
# Ensure the URL uses HTTPS
url = ip_address
if not url.startswith("https://"):
# Convert http:// to https:// or add https:// if no protocol specified
if url.startswith("http://"):
url = "https://" + url[7:]
else:
url = "https://" + url
if not url.endswith(f":{port}"):
url = url + f":{port}"
verify_ssl = False
if not verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
token = authenticate(url)
if not token:
return
time.sleep(3)
set_ssh_status(url, token)

View File

@@ -10,11 +10,11 @@ from azure.iot.hub.models import Twin, TwinProperties
load_dotenv(override=True)
ip_address_prefix = "10.81.60."
ip_address_prefix = "10.81.56."
ssh_command = "hostname"
csv_filename = "Grandpuits_01.csv"
SITE_NAME = "Grandpuits"
csv_filename = "DK2_01.csv"
SITE_NAME = "DK2"
ssh_username = os.getenv("DEFAULT_CUBE_LINUX_ADMIN_USER")
ssh_password = os.getenv("DEFAULT_CUBE_LINUX_ADMIN_PASSWORD")

View File

@@ -0,0 +1,141 @@
import csv
import paramiko
import time
from cube_activate_ssh_port import activate_ssh
from dotenv import load_dotenv
import os
import re
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.models import Twin, TwinProperties
load_dotenv(override=True)
ssh_username = os.getenv("DEFAULT_CUBE_LINUX_ADMIN_USER")
ssh_password = os.getenv("DEFAULT_CUBE_LINUX_ADMIN_PASSWORD")
CONNECTION_STRING = str(os.getenv("CONNECTION_STRING_INOX_PROD"))
def execute_ssh_command(ip, port, command):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(ip, port=port, username=ssh_username, password=ssh_password, allow_agent=False, look_for_keys=False)
stdin, stdout, stderr = client.exec_command(command)
result = stdout.read().decode().lower().strip()
return result
except Exception as e:
print(f"SSH Error: {str(e)}")
raise
finally:
client.close()
def update_cloud_config(ip, new_content):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(ip, port=11022, username=ssh_username, password=ssh_password, allow_agent=False, look_for_keys=False)
stdin, stdout, stderr = client.exec_command(f'sudo -S bash -c \'cat > /etc/cube/config-azure.properties << EOF\n{new_content}\nEOF\'\n')
stdin.write(ssh_password + "\n")
stdin.flush()
stdoutput = [line for line in stdout]
stderroutput = [line for line in stderr]
for output in stdoutput:
print(output.strip())
except Exception as e:
print(f"SSH Error: {str(e)}")
raise
finally:
client.close()
def main():
SITE_NAME = "LIBERTY"
ip_address = "192.168.15.158"
https_start_port = 8080
https_end_port = 8096
https_port_range = list(range(https_start_port, https_end_port + 1))
print(f"{https_port_range}")
ssh_start_port = 8180
ssh_start_end = 8196
ssh_port_range = list(range(ssh_start_port, ssh_start_end + 1))
print(f"{ssh_port_range}")
print(f"Site: {SITE_NAME}")
print(f"From {ip_address}:{str(https_start_port)} to {ip_address}:{str(https_end_port)}")
file_numbering = 0
while os.path.exists(SITE_NAME + "_" + str(file_numbering) + ".csv"):
file_numbering = file_numbering + 1
csv_filename = SITE_NAME + "_" + str(file_numbering) + ".csv"
print(f"Logging results to {csv_filename}")
with open(csv_filename, mode="w", newline="") as file:
writer = csv.writer(file)
writer.writerow(["Number", "IP address", "Cube ID", "Environment", "Correct configuration"])
registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING)
results = []
for i in [0]: # range(0, len(https_port_range)):
ip_address = f"{ip_address}"
print(f"Activating SSH for {ip_address}:{https_port_range[i]} ", end=" ")
try:
activate_ssh(ip_address, https_port_range[i])
except Exception as e:
writer.writerow([i, f"{ip_address}:{https_port_range[i]}", "UNREACHABLE", "NA", "NA"])
file.flush()
continue
ssh_command = "hostname"
print(f"Executing {ssh_command} for {ip_address}:{ssh_port_range[i]}:", end=" ")
try:
cube_id = execute_ssh_command(ip_address, ssh_port_range[i], ssh_command)
except Exception as e:
print("Failed!")
writer.writerow([i, ip_address, "UNREACHABLE", "NA", "NA"])
file.flush()
continue
print(cube_id)
ssh_command = "grep \"connection-string\" /etc/cube/config-azure.properties"
print(f"Getting configured Connection String for {ip_address}:", end=" ")
try:
connection_string = execute_ssh_command(ip_address, ssh_port_range[i], ssh_command)
if connection_string == "":
raise Exception("No Connection String was extracted!")
iothub_match = re.search(r"hostname\\=(.*?);", connection_string, re.IGNORECASE)
iothub = iothub_match.group(1) if iothub_match else None
if iothub.lower() == "IotHub-CUBE-PROD.azure-devices.net".lower():
migration = "SAFT"
elif iothub.lower() == "iot-ingest-ess-prod.azure-devices.net".lower():
migration = "INOX"
else:
migration = "NONE"
device_id_match = re.search(r"deviceid\\=(.*?);", connection_string, re.IGNORECASE)
cloud_cube_id = device_id_match.group(1) if device_id_match else None
if cloud_cube_id.lower() == cube_id.lower():
status = "CORRECT"
else:
status = "INCORRECT"
except Exception as e:
print(e)
migration = "NONE"
status = "INCORRECT"
finally:
print(f"{migration} {status}")
writer.writerow([i, ip_address, cube_id, migration, status])
file.flush()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,336 @@
import pandas as pd
import requests
from urllib3.exceptions import InsecureRequestWarning
import jq
import json
import os
from dotenv import load_dotenv
import time
from tqdm import tqdm
load_dotenv()
# Function to authenticate and get token
def authenticate(device_ip, payload):
auth_url = f"https://{device_ip}:8443/api/v1/auth"
try:
response = requests.post(auth_url, json=payload, verify=False)
if response.status_code == 200:
token = response.json()["data"]["token"]
#print(f"Authentication successful. Token received: {token}")
#print(" authenticated!")
return token
else:
print(f"Authentication failed. Status code: {response.status_code}")
return None
except Exception as e:
raise Exception("Authentication failed!") from e
# Function to update connection string through PATCH request
def update_connection_string(device_ip, token, connection_string):
headers = {
"mx-api-token": token
}
payload = {
"provisioning": {
"source": "manual",
"connectionString": connection_string,
"enable": True
}
}
patch_url = f"https://{device_ip}:8443/api/v1/azure-iotedge"
try:
response = requests.patch(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
print(f"PATCH request successful for device {device_ip}")
else:
print(f"Failed to send PATCH request to device {device_ip}. Status code: {response.status_code}")
except Exception as e:
raise Exception("Update connection string failed!") from e
# Function to update NTP
def update_ntp(device_ip, token):
headers = {
"mx-api-token": token
}
payload = {
"ntp": {
"source": "timeserver",
"server": "10.84.171.254",
"enable": True
}
}
payload = {
"timezone": "America/Chicago"
}
patch_url = f"https://{device_ip}:8443/api/v1/device/time"
try:
response = requests.patch(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
json_data = json.loads(response.content.decode())
time = json_data['data']['time']
timezone = json_data['data']['timezone']
last = json_data['data']['lastUpdateTime']
server = json_data['data']['ntp']['server']
enabled = json_data['data']['ntp']['enable']
print(time + " " + timezone + " " + last + " " + server + " " + str(enabled))
else:
json_data = json.loads(response.content.decode())
except Exception as e:
raise Exception("Update NTP failed!") from e
# Function to create an upgrade job
def create_upgrade_job(device_ip, token, upgrade_url):
headers = {
"mx-api-token": token
}
payload = {
"download": True,
"install": True,
"url": upgrade_url,
}
patch_url = f"https://{device_ip}:8443/api/v1/upgrades"
try:
response = requests.post(patch_url, json=payload, headers=headers, verify=False)
json_data = json.loads(response.content.decode())
id = json_data['data']['id']
return id
except Exception as e:
raise Exception("Create upgrade job failed") from e
# Function to get upgrade job
def get_upgrade_job(device_ip, token, id):
headers = {
"mx-api-token": token
}
payload = {
}
patch_url = f"https://{device_ip}:8443/api/v1/upgrades/{id}"
try:
response = requests.get(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
json_data = json.loads(response.content.decode())
getid = json_data['data']['id']
created = json_data['data']['createdAt']
started = json_data['data']['startedAt']
cur_status = json_data['data']['state']
current_tasks = json_data['data']['completedTask']
total_tasks = json_data['data']['totalTask']
print(f"Upgrade job #{str(getid)} ({str(current_tasks)}/{str(total_tasks)}) {str(cur_status)}")
else:
print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}")
print(response.content)
except Exception as e:
raise Exception("Failed getting upgrade job!") from e
def get_upgrade_jobs(device_ip, token):
headers = {
"mx-api-token": token
}
payload = {
}
patch_url = f"https://{device_ip}:8443/api/v1/upgrades"
try:
response = requests.get(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
json_data = json.loads(response.content.decode())
count = json_data['count']
print(str(count))
for i in range(count):
getid = json_data['data'][i]['id']
created = json_data['data'][i]['createdAt']
cur_status = json_data['data'][i]['state']
current_tasks = json_data['data'][i]['completedTask']
total_tasks = json_data['data'][i]['totalTask']
print("JOB #" + str(getid) + " " + str(current_tasks) + "/" + str(total_tasks))
print("CREATED ON: " + str(created))
print("CURRENT STATUS: " + str(cur_status))
else:
print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}")
print(response.content)
except Exception as e:
raise Exception("Failed getting all upgrade jobs!") from e
def get_last_upgrade_job(device_ip, token):
headers = {
"mx-api-token": token
}
payload = {
}
patch_url = f"https://{device_ip}:8443/api/v1/upgrades"
try:
response = requests.get(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
json_data = json.loads(response.content.decode())
last_job = int(json_data['count'] - 1)
getid = json_data['data'][last_job]['id']
created = json_data['data'][last_job]['createdAt']
cur_status = json_data['data'][last_job]['state']
current_tasks = json_data['data'][last_job]['completedTask']
total_tasks = json_data['data'][last_job]['totalTask']
print("JOB #" + str(getid) + " " + str(current_tasks) + "/" + str(total_tasks))
print("CREATED ON: " + str(created))
print("CURRENT STATUS: " + str(cur_status))
for task in range(total_tasks):
if json_data['data'][last_job]['tasks'][task]['type'] == "download":
print(f"Downloaded: {json_data['data'][last_job]['tasks'][task]['progress']}%")
return getid
else:
print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}")
print(response.content)
except Exception as e:
raise Exception("Failed getting last upgrade job!") from e
# Function to start upgrade job
def start_upgrade_job(device_ip, token, id):
headers = {
"mx-api-token": token
}
payload = {
}
patch_url = f"https://{device_ip}:8443/api/v1/upgrades/{id}/start"
try:
response = requests.put(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
json_data = json.loads(response.content.decode())
curid = json_data['data']['id']
startedat = json_data['data']['startedAt']
print("Job #" + str(curid) + " started on " + str(startedat))
else:
print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}")
print(response.content.decode())
except Exception as e:
raise Exception("Failed starting upgrade job!") from e
# Function to send a PUT request
def put_API(device_ip, token):
headers = {
"mx-api-token": token
}
payload = {
}
patch_url = "" #f"https://{device_ip}:8443/api/v1/upgrades/3/start"
try:
if patch_url == "":
raise Exception("Empty URL!")
response = requests.put(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
json_data = json.loads(response.content.decode())
print(json_data['data']['firmwareVersion'])
else:
print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}")
print(response.content.decode())
except Exception as e:
raise Exception("Failed sending PUT request!") from e
def get_version(device_ip, token):
headers = {
"mx-api-token": token
}
payload = {
}
patch_url = f"https://{device_ip}:8443/api/v1/device/general"
try:
response = requests.get(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
json_data = json.loads(response.content.decode())
hostname = json_data['data']['hostName']
serial = json_data['data']['serialNumber']
version = json_data['data']['firmwareVersion']
description = json_data['data']['description']
print(hostname + " " + serial + " " + version + " " + description)
else:
print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}")
print(response.content.decode())
except Exception as e:
raise Exception("Failed getting version!") from e
def get_time(device_ip, token):
headers = {
"mx-api-token": token
}
payload = {
}
patch_url = f"https://{device_ip}:8443/api/v1/device/time"
try:
response = requests.get(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
json_data = json.loads(response.content.decode())
time = json_data['data']['time']
timezone = json_data['data']['timezone']
last = json_data['data']['lastUpdateTime']
print(time + " " + timezone + " " + last)
else:
print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}")
print(response.content.decode())
except Exception as e:
raise Exception("Failed getting time!") from e
def delete_API(device_ip, token):
headers = {
"mx-api-token": token
}
payload = {
}
patch_url = "" #f"https://{device_ip}:8443/api/v1/upgrades/5"
try:
if patch_url == "":
raise Exception("Empty URL!")
response = requests.delete(patch_url, json=payload, headers=headers, verify=False)
print(response.status_code)
json_data = json.loads(response.content.decode())
json_str = json.dumps(json_data, indent=2)
print(json_str)
except Exception as e:
raise Exception("Failed sending DELETE request!") from e
def visual_wait(total_seconds):
# "total=total_seconds" sets the bar max value
# "bar_format" removes the default stats to keep it clean
with tqdm(total=total_seconds, bar_format="{desc} [{bar}]") as pbar:
# Loop backwards from 200 down to 1
for remaining in range(total_seconds, 0, -1):
# Manually update the text description to show the countdown
pbar.set_description_str(f"Waiting {remaining}s")
# Advance the visual bar by 1 step
pbar.update(1)
time.sleep(1)
# Final update to show 0s at the very end
pbar.set_description_str(f"Finished waiting!")
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
default_user = str(os.getenv("DEFAULT_MOXA_USER"))
default_password = str(os.getenv("DEFAULT_MOXA_PASSWORD"))
moxa_range = [i for i in range(132, 159) ]
moxa_range.remove(137)
for i in moxa_range:
device_ip_address = str("10.84.157." + str(i))
print(device_ip_address)
payload_auth = {
"acceptEULA": True,
"name": default_user,
"password": default_password
}
upgrade_url = "http://10.84.157.137:8080/1.8.1.yaml"
try:
token = authenticate(device_ip_address, payload_auth)
if token:
get_version(device_ip_address, token)
get_last_upgrade_job(device_ip_address, token)
# id = create_upgrade_job(device_ip_address, token, upgrade_url)
# visual_wait(3)
# start_upgrade_job(device_ip_address, token, id)
# visual_wait(3)
# get_upgrade_job(device_ip_address, token, id)
# visual_wait(30)
except Exception as e:
print(f"Exception for {device_ip_address}: {e}")
continue

View File

@@ -5,6 +5,7 @@ import jq
import json
import os
from dotenv import load_dotenv
import time
load_dotenv()
@@ -84,6 +85,7 @@ def send_upgrade_request(device_ip, token, upgrade_url):
patch_url = f"https://{device_ip}:8443/api/v1/upgrades"
response = requests.post(patch_url, json=payload, headers=headers, verify=False)
json_data = json.loads(response.content.decode())
#print(json.dumps(json_data, indent=4, sort_keys=True))
id = json_data['data']['id']
return id
@@ -138,6 +140,38 @@ def get_upgrade_jobs(device_ip, token):
print("JOB #" + str(getid) + " " + str(current_tasks) + "/" + str(total_tasks))
print("CREATED ON: " + str(created))
print("CURRENT STATUS: " + str(cur_status))
print(json.dumps(json_data, indent=4, sort_keys=True))
else:
print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}")
print(response.content)
def get_last_job(device_ip, token):
headers = {
"mx-api-token": token
}
payload = {
}
patch_url = f"https://{device_ip}:8443/api/v1/upgrades"
response = requests.get(patch_url, json=payload, headers=headers, verify=False)
if response.status_code == 200:
#print(f"GET request successful for device {device_ip}")
#print(json_str)
#print(json_data['data'][json_data['count'] - 1]['parameter']['url'], json_data['data'][json_data['count'] - 1]['state'], json_data['count'])
json_data = json.loads(response.content.decode())
last_job = int(json_data['count'] - 1)
getid = json_data['data'][last_job]['id']
created = json_data['data'][last_job]['createdAt']
cur_status = json_data['data'][last_job]['state']
current_tasks = json_data['data'][last_job]['completedTask']
total_tasks = json_data['data'][last_job]['totalTask']
print("JOB #" + str(getid) + " " + str(current_tasks) + "/" + str(total_tasks))
print("CREATED ON: " + str(created))
print("CURRENT STATUS: " + str(cur_status))
for task in range(total_tasks):
if json_data['data'][last_job]['tasks'][task]['type'] == "download":
print(f"Downloaded: {json_data['data'][last_job]['tasks'][task]['progress']}%")
#print(json.dumps(json_data, indent=4, sort_keys=True))
return getid
else:
print(f"Failed to send GET request to device {device_ip}. Status code: {response.status_code}")
print(response.content)
@@ -281,28 +315,47 @@ requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
default_user = str(os.getenv("DEFAULT_MOXA_USER"))
default_password = str(os.getenv("DEFAULT_MOXA_PASSWORD"))
moxa_range = [i for i in range(148, 159) if i not in (136, 137)]
for i in moxa_range:
device_ip_address = str("10.84.157." + str(i))
print(device_ip_address, end=" ")
for i in range(193, 222):
upgrade_url = "https://files.thingsprocloud.com/package/Upgrade_AIG-301_2.4.0-4020_IMG_1.4_to_1.5.deb.yaml"
payload_auth = {
"acceptEULA": True,
"name": default_user,
"password": default_password
}
device_ip_address = str("10.84.171." + str(i))
#print(device_ip_address, end="")
token = authenticate(device_ip_address, payload_auth)
print(hash(token))
upgrade_url = "http://10.84.157.137:8080/1.6.0.yaml"
if token:
#id = send_upgrade_request(device_ip_address,token,upgrade_url)
#print(id)
#get_upgrade_job(device_ip_address, token, 6)
#start_upgrade_job(device_ip_address, token, id)
#put_API(device_ip_address, token)
#patch_time(device_ip_address,token)
#get_time(device_ip_address, token)
#delete_API(device_ip_address,token)
get_API(device_ip_address, token)
#send_upgrade_request(device_ip_address, token, upgrade_url)
id = get_last_job(device_ip_address, token)
start_upgrade_job(device_ip_address, token, id)
time.sleep(300)
#input("Continue?")
else:
print("Authentication failed!")
raise(Exception("Authentication failed!"))
# upgrade_url = "https://10.84.157.137/Upgrade_AIG-301_2.5.0-4404_IMG_1.5_to_1.6.0.yaml"
# device_ip_address = str("10.84.157." + str(i))
# print(f"{device_ip_address}")
# #print(device_ip_address, end="")
# token = authenticate(device_ip_address, payload_auth)
# if token:
# #id = send_upgrade_request(device_ip_address,token,upgrade_url)
# #print(id)
# get_last_job(device_ip_address, token)
# #start_upgrade_job(device_ip_address, token, id)
# #put_API(device_ip_address, token)
# #patch_time(device_ip_address,token)
# #get_time(device_ip_address, token)
# #delete_API(device_ip_address,token)
# #get_API(device_ip_address, token)
# else:
# print("Authentication failed!")

View File

@@ -10,6 +10,28 @@ import os
import logging
import sys
# from 1 to 5 (Modbus)
# PCS
# AC_Resistance
# DC_Resistance
# Hz
# DCV
# VL1L2
# VL2L3
# VL3L1
# OnEvent
# ACSw_Status
# ActErrNoX
# DCSw_3_Status
# Fault_Id_tmp
# Fault_Status
# InvSt_tmp
# MV_Transformer_Gas_Monitoring
# MV_Transformer_Oil_Level_Trip
# MV_Transformer_Pressure
# MV_Transformer_Pressure_Trip
def generate_oe(device_id, modbus_tags):
return {
'description': '',
@@ -112,7 +134,6 @@ for device in devices:
current_device_modules = registry_manager.get_modules(device.deviceId)
for module in current_device_modules:
if (module.module_id == module_id):
#print(f"{module.module_id}")
device.setModule(module)
payload = '{"method":"GET", "path":"/azure-iotedge/messages"}'
try:
@@ -120,7 +141,6 @@ for device in devices:
response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=direct_method)
device_messages = json.dumps(response.payload, indent = 2)
parsed_messages = json.loads(device_messages)
#print("Parsed messages")
message_ids_with_tags = {}
tags_to_on_event = defaultdict(set)
for message in parsed_messages["data"]:
@@ -131,6 +151,7 @@ for device in devices:
tags = message.get("tags", {}).get("modbus_tcp_master", {})
# This block seems list existing OE topics and their content.
for asset, tag_list in tags.items():
if "InvSt" in tag_list and "OE" in topic_name:
message_ids_with_tags.setdefault(message_id, message)
@@ -140,68 +161,69 @@ for device in devices:
message_ids_with_tags.setdefault(message_id, message)
tags_to_on_event[asset].add("Fault_Id")
print(f"{device.getDeviceId()} {device.getSite()} {device.getNumber()} {message_id} {topic_name} {asset} Fault_Id")
# print(f"{device.getDeviceId()} {device.getSite()} {device.getNumber()}")
# print(f"Messages to update: {', '.join(str(key) for key in message_ids_with_tags.keys())}")
# for asset, tag in tags_to_on_event.items():
# print(f"Alarms to make on event:")
# print(f"{asset}: {', '.join(tag)}")
# print(f"Finishing on {device}")
# oe_exists = False
# twelve_exists = False
print(f"{device.getDeviceId()} {device.getSite()} {device.getNumber()}")
print(f"Messages to update: {', '.join(str(key) for key in message_ids_with_tags.keys())}")
for asset, tag in tags_to_on_event.items():
print(f"Alarms to make on event:")
print(f"{asset}: {', '.join(tag)}")
print(f"Finishing on {device}")
# for message in message_ids_with_tags.values():
# if message['outputTopic'] == "REL_OE_PCS":
# oe_exists = True
# if message['outputTopic'] == "REL_OE_PCS_12H":
# twelve_exists = True
oe_exists = False
twelve_exists = False
# if (oe_exists == False or twelve_exists == False):
# first_message = next(iter(message_ids_with_tags.values()))
# properties = first_message['properties']
# cdid = next((item['value'] for item in properties if item['key'] == 'cdid'))
# print(f"{cdid}")
for message in message_ids_with_tags.values():
if message['outputTopic'] == "REL_OE_PCS":
oe_exists = True
if message['outputTopic'] == "REL_OE_PCS_12H":
twelve_exists = True
# modbus_tags = {
# asset: sorted(list(tags))
# for asset, tags in tags_to_on_event.items()
# }
# new_payload = ""
# if (oe_exists == False):
# new_payload = '{"method":"POST", "path":"/azure-iotedge/messages", "requestBody":' + json.dumps(generate_oe(cdid, modbus_tags)) + '}'
# new_direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(new_payload))
# new_response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=new_direct_method)
# print(json.dumps(new_response.payload, indent = 2))
if (oe_exists == False or twelve_exists == False):
first_message = next(iter(message_ids_with_tags.values()))
properties = first_message['properties']
cdid = next((item['value'] for item in properties if item['key'] == 'cdid'))
print(f"{cdid}")
# if (twelve_exists == False):
# new_payload = '{"method":"POST", "path":"/azure-iotedge/messages", "requestBody":' + json.dumps(generate_12h(cdid, modbus_tags)) + '}'
# new_direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(new_payload))
# new_response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=new_direct_method)
# print(json.dumps(new_response.payload, indent = 2))
modbus_tags = {
asset: sorted(list(tags))
for asset, tags in tags_to_on_event.items()
}
new_payload = ""
if (oe_exists == False):
new_payload = '{"method":"POST", "path":"/azure-iotedge/messages", "requestBody":' + json.dumps(generate_oe(cdid, modbus_tags)) + '}'
new_direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(new_payload))
new_response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=new_direct_method)
print(json.dumps(new_response.payload, indent = 2))
# for id, message in message_ids_with_tags.items():
# if "OE" in message['outputTopic']:
# continue
# tags_to_remove = {"InvSt", "Fault_Id"}
# for protocol, versions in message['tags'].items():
# for version, tag_list in versions.items():
# message['tags'][protocol][version] = [
# tag for tag in tag_list if tag not in tags_to_remove
# ]
# print("\n\n\nREMOVING\n\n\n")
# new_payload = '{"method":"PUT", "path":"/azure-iotedge/messages/' + str(id) + '", "requestBody":' + json.dumps(message) + '}'
# print(new_payload)
# new_direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(new_payload))
# new_response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=new_direct_method)
# print(json.dumps(new_response.payload, indent = 2))
# print(message['tags'])
if (twelve_exists == False):
new_payload = '{"method":"POST", "path":"/azure-iotedge/messages", "requestBody":' + json.dumps(generate_12h(cdid, modbus_tags)) + '}'
new_direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(new_payload))
new_response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=new_direct_method)
print(json.dumps(new_response.payload, indent = 2))
for id, message in message_ids_with_tags.items():
if "OE" in message['outputTopic']:
continue
tags_to_remove = {"InvSt", "Fault_Id"}
for protocol, versions in message['tags'].items():
for version, tag_list in versions.items():
message['tags'][protocol][version] = [
tag for tag in tag_list if tag not in tags_to_remove
]
print("\n\n\nREMOVING\n\n\n")
new_payload = '{"method":"PUT", "path":"/azure-iotedge/messages/' + str(id) + '", "requestBody":' + json.dumps(message) + '}'
print(new_payload)
new_direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(new_payload))
new_response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=new_direct_method)
print(json.dumps(new_response.payload, indent = 2))
print(message['tags'])
# print("\n\n\nREMOVING\n\n\n")
# new_payload = '{"method":"PUT", "path":"/azure-iotedge/messages/' + str(id) + '", "requestBody":' + json.dumps(message) + '}'
# print(new_payload)
# new_direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(new_payload))
# new_response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=new_direct_method)
# print(json.dumps(new_response.payload, indent = 2))
print("\n\n\nREMOVING\n\n\n")
new_payload = '{"method":"PUT", "path":"/azure-iotedge/messages/' + str(id) + '", "requestBody":' + json.dumps(message) + '}'
print(new_payload)
new_direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(new_payload))
new_response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=new_direct_method)
print(json.dumps(new_response.payload, indent = 2))
except Exception as e:
logger.error(str(e))

366
Python/on_event_topics_2.py Normal file
View File

@@ -0,0 +1,366 @@
from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.protocol.models import QuerySpecification, Module
from azure.iot.hub.models import CloudToDeviceMethod, CloudToDeviceMethodResult
from dotenv import load_dotenv
from isight_device import iSightDevice
from collections import defaultdict
import copy
import json
import os
import logging
import sys
import re
# from 1 to 5 (Modbus)
# PCS
# AC_Resistance
# DC_Resistance
# Hz
# DCV
# VL1L2
# VL2L3
# VL3L1
def resolve_missing_topic(topic_name, available_list):
"""
Prompts user to select a topic or create a new one.
Returns the selected ID or 0 if creation is requested.
"""
print(f"Topic {topic_name} not found!")
print(f"0. Create a new {topic_name} topic")
# Display available options
for idx, item in enumerate(available_list):
print(f"{idx + 1}. {item['outputTopic']}")
while True:
try:
choice = int(input(f"Select an option for {topic_name}: "))
if choice == 0:
return 0
if 1 <= choice <= len(available_list):
selected = available_list[choice - 1]
return selected['id']
print("Invalid number, please select from the list.")
except ValueError:
print("Please enter a valid number.")
def generate_oe(device_id, modbus_tags):
return {
'description': '',
'outputTopic': 'REL_OE_PCS',
'properties': [
{'key': 'cdid', 'value': device_id},
{'key': 'deviceType', 'value': 'AC_GATEWAY'}
],
'tags': {
'modbus_tcp_master': modbus_tags
},
'sendOutThreshold': {
'mode': 'immediately',
'size': 4096,
'time': 60,
'sizeIdleTimer': {
'enable': True,
'time': 60
}
},
'minPublishInterval': 0,
'samplingMode': 'allChangedValues',
'customSamplingRate': False,
'pollingInterval': 0,
'onChange': True,
'enable': True,
'format': (
'{deviceId:("' + device_id +
'"),emissionDate:(now|todateiso8601),deviceType:("AC_GATEWAY"),'
'metrics:[{idAsset:(.srcName),name:(.tagName),value:.dataValue,'
'timestamp:((.ts/1000000)|todateiso8601)}]}'
)
}
def generate_12h(device_id, modbus_tags):
return {
'description': '',
'outputTopic': 'REL_OE_PCS_12H',
'properties': [
{'key': 'cdid', 'value': device_id},
{'key': 'deviceType', 'value': 'AC_GATEWAY'}
],
'tags': {
'modbus_tcp_master': modbus_tags
},
'sendOutThreshold': {
'mode': 'byTime',
'size': 4096,
'time': 43200,
'sizeIdleTimer': {
'enable': True,
'time': 60
}
},
'minPublishInterval': 0,
'samplingMode': 'latestValues',
'customSamplingRate': False,
'pollingInterval': 43200,
'onChange': False,
'enable': True,
'format': (
'{deviceId:("' + device_id +
'"),emissionDate:(now|todateiso8601),deviceType:("AC_GATEWAY"),'
'metrics:[{idAsset:(.srcName),name:(.tagName),value:.dataValue,'
'timestamp:((.ts/1000000)|todateiso8601)}]}'
)
}
# Configure logger to use stderr
logging.basicConfig(
level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s',
stream=sys.stderr # <- Key line
)
logger = logging.getLogger(__name__)
# .env containing secrets to authorize access to IoT Hub
load_dotenv()
CONNECTION_STRING = str(os.getenv("CONNECTION_STRING_INOX_PROD"))
if CONNECTION_STRING == "":
print("Provide a connection string for the Iot Hub before running the script!")
exit(13)
# IoT Edge module and method that allow Moxa AIG-301 management
module_id = "thingspro-agent"
method_name = "thingspro-api-v1"
# Connection to IoT Hub and listing of IoT Edge device (so AIG-301 in the scope of SAFT)
registry_manager = IoTHubRegistryManager.from_connection_string(CONNECTION_STRING)
query_spec = QuerySpecification(query="SELECT * FROM devices WHERE capabilities.iotEdge = true ")
query_result = registry_manager.query_iot_hub(query_spec)
# Sorting devices by site
devices = []
try:
for item in query_result.items:
if str(item.connection_state) == "Connected":
currentDevice = iSightDevice(str(item.device_id), str(item.tags['site']), int(item.tags['number']), str(item.tags['version']))
devices.append(iSightDevice(str(item.device_id), str(item.tags['site']), int(item.tags['number']), str(item.tags['version'])))
devices.sort(key = lambda d: (d.site, d.number))
else:
print(f"Device not connected: {str(item.tags['site'])} {int(item.tags['number'])} {str(item.device_id)}")
except KeyboardInterrupt:
# This block runs when user presses Ctrl+C
print("\nProgram killed by user (Ctrl+C).")
sys.exit(0)
# List of the tags that are a status or alert
on_event_tags = [
"1365",
"AC_Insulation_Status",
"ACSw_Status",
"ActErrNo1",
"ActErrNo10",
"ActErrNo2",
"ActErrNo3",
"ActErrNo4",
"ActErrNo5",
"ActErrNo6",
"ActErrNo7",
"ActErrNo8",
"ActErrNo9",
"BESS_Control_Mode",
"BESS_Control_Mode_2",
"Calibration_Required",
"CO_Sensor_1",
"CO_Sensor_2",
"CO_Sensor_3",
"CO_Sensor_4",
"CO_Sensor_5",
"Combi_Detector_1",
"Combi_Detector_2",
"Combi_Detector_3",
"Combi_Detector_4",
"Combi_Detector_5",
"Combi_Detector_6",
"DC_Insulation_Status",
"DCSw_1_Status",
"DCSw_2_Status",
"DCSw_3_Status",
"Digital_Input_Line_0",
"DIL0",
"DIL1",
"DIL2",
"Door_Open",
"EMS_Control",
"Energy_Fault",
"ESS_Status",
"Extinguisher_Activated",
"Fault_Id_tmp",
"Fault_Module_Id",
"Fault_Status",
"General_Fault",
"InvSt_tmp",
"Last_Event",
"Loss_of_Agent",
"Manual_mode",
"Manual_Pull_Station",
"MV_Transformer_Gas_Monitoring",
"MV_Transformer_Oil_Level_Trip",
"MV_Transformer_Pressure",
"MV_Transformer_Pressure_Trip",
"MV_Transformer_Temperature_Alarm",
"MV_Transformer_Temperature_Warning",
"NbModule",
"NbModule_Run",
"Reg_Request",
"Version",
"Warn_Id"
]
try:
for device in devices:
print(f"{device.getSite()} {device.getNumber()} {device.getDeviceId()}")
modbus_tags_to_set_on_event = {}
id_on_event_topic = -1
id_12h_topic = -1
current_device_modules = registry_manager.get_modules(device.deviceId)
for module in current_device_modules:
if (module.module_id == module_id):
device.setModule(module)
# This block get all the Modbus tags available and cross check them with the list of tags that require changes
payload = '{"method":"GET", "path":"/tags/list"}'
try:
direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(payload))
response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=direct_method)
device_tags = json.dumps(response.payload, indent = 2)
parsed_tags = json.loads(device_tags)
for item in parsed_tags['data']:
if item['prvdName'] == "modbus_tcp_master" and item['tagName'] in on_event_tags:
modbus_tags_to_set_on_event.setdefault(item['srcName'], []).append(item['tagName'])
except Exception as e:
logger.error(f"Exception when getting list of device's Modbus tags that can be set On Event:\n{str(e)}")
payload = '{"method":"GET", "path":"/azure-iotedge/messages"}'
try:
direct_method = CloudToDeviceMethod(method_name=method_name, payload=json.loads(payload))
response = registry_manager.invoke_device_module_method(device_id=device.deviceId, module_id=module_id, direct_method_request=direct_method)
device_topics = json.dumps(response.payload, indent = 2)
parsed_topics = json.loads(device_topics)
for item in parsed_topics["data"]:
if item['enable'] and item["outputTopic"] == "REL_OE_PCS":
id_on_event_topic = item['id']
if item['enable'] and item["outputTopic"] == "REL_OE_PCS_12H":
id_12h_topic = item['id']
available_topics = [
item for item in parsed_topics["data"]
if item['enable']
and item['id'] != id_on_event_topic
and item['id'] != id_12h_topic
]
if id_on_event_topic == -1:
id_on_event_topic = resolve_missing_topic("REL_OE_PCS", available_topics)
if id_12h_topic == -1:
id_12h_topic = resolve_missing_topic("REL_OE_PCS_12H", available_topics)
targets = [
{"id": id_on_event_topic, "name": "OnEvent"},
{"id": id_12h_topic, "name": "12h"}
]
for target in targets:
target_id = target["id"]
target_name = target["name"]
if target_id > 0:
topic_item = next((item for item in parsed_topics["data"] if item["id"] == target_id), None)
if topic_item:
original_tags = topic_item.get("tags", {}).get("modbus_tcp_master", {})
else: # Pure error handling, detected ID does not exist
print(f"Error: ID {target_id} not found. Skipping.")
continue
else: # Not error handling, target_id == 0 means creation of new topic
original_tags = {}
final_tag_list = copy.deepcopy(original_tags)
for device_name, tags_to_add in modbus_tags_to_set_on_event.items():
if device_name not in final_tag_list:
final_tag_list[device_name] = []
current_list = final_tag_list[device_name]
for tag in tags_to_add:
if tag not in current_list:
current_list.append(tag)
# CASE A: CREATE NEW
if target_id == 0:
print(f"[{target_name}] ID is 0 -> ACTION: CREATE NEW TOPIC")
elif final_tag_list != original_tags:
print(f"[{target_name}] ID {target_id} -> ACTION: UPDATE EXISTING")
else:
print(f"[{target_name}] ID {target_id} -> ACTION: NONE (Already up to date)")
# Removing tags from existing topics: cleaned_topic
protected_ids = [id_on_event_topic, id_12h_topic]
for item in parsed_topics["data"]:
if item['id'] in protected_ids or item['enable'] == False:
continue
original_tags = item.get("tags", {}).get("modbus_tcp_master", {})
try:
idAsset = re.search(r"idAsset\s*:\s*\((.*?)\)", item.get("format", "")).group(1).strip()
except Exception as e:
print(f"Exception while getting idAsset: {item}")
continue
if not original_tags:
continue
final_tag_list = copy.deepcopy(original_tags)
modification_needed = False
for device_name, tags_to_remove in modbus_tags_to_set_on_event.items():
if device_name in final_tag_list:
current_list = final_tag_list[device_name]
new_list = [t for t in current_list if t not in tags_to_remove]
if len(new_list) != len(current_list):
final_tag_list[device_name] = new_list
modification_needed = True
if not final_tag_list[device_name]:
del final_tag_list[device_name]
if modification_needed:
print(f"-> Applying update for topic: {item['outputTopic']} (ID: {item['id']} & {idAsset})")
except Exception as e:
logger.error(f"Exception when getting all Telemetry Topics:\n{str(e)}")
user_input = 'Y' # input("Continue? Y/n ").lower().strip()
if user_input == 'n':
print("Stopping loop.")
break
except KeyboardInterrupt:
# This block runs when user presses Ctrl+C
print("\nProgram killed by user (Ctrl+C).")
sys.exit(0)

View File

@@ -11,3 +11,4 @@ fabric
ruamel.yaml
netmiko
pexpect
tqdm

View File

@@ -175,6 +175,14 @@ def find_config_value(config_content, option):
# If the loop finishes without finding the option, return None
return None
def cloud_configuration_csv(result):
lightTelemetry = find_config_value(result, "light-telemetry")
telemetryOn = find_config_value(result, "telemetry-on")
compressionEnabled = find_config_value(result, "compression-enabled")
remoteUpdateOn = find_config_value(result, "remote-update-on")
connectionString = find_config_value(result, "connection-string")
return f"{lightTelemetry};{telemetryOn};{compressionEnabled};{remoteUpdateOn};{connectionString};"
def cloud_configuration_check(hostname, result, iot_hub, proxy_host, proxy_port):
print(f"\tLight telemetry:", end=" ", flush=True)
status = find_config_value(result, "light-telemetry")
@@ -185,7 +193,7 @@ def cloud_configuration_check(hostname, result, iot_hub, proxy_host, proxy_port)
print(f"\tTelemetry:", end=" ", flush=True)
status = find_config_value(result, "telemetry-on")
if status == "true":
if status == "False":
print(f"", end="\n", flush=True)
else:
print(f"")
@@ -437,12 +445,55 @@ def write_remote_config_base64_sudo(c, remote_path, content, sudo_pass, user_own
# Re-raise the exception for the main loop.
raise
def check_for_specific_curl_error(c):
def execute_command(c, command):
"""Executes a simple command on the remote device."""
try:
result = c.run(command, hide=True)
return result.stdout
except Exception as e:
raise
"""
Checks for the specific cURL exit code 35.
Args:
c: The connection object.
Returns:
True if the expected error is caught, False otherwise.
"""
try:
# We call execute_command, but expect it to fail and raise an exception
result = execute_command(c, "curl -m 15 -x https://10.81.35.126:8080 https://iot-ingest-ess-prod.azure-devices.net")
# If the command somehow succeeds, the expected error did not occur.
print(f"Success (unexpected): {result.strip()}", end="\n", flush=True)
return False
except Exception as e:
# The command failed as expected. Now, check if it's the RIGHT failure.
error_message = str(e)
# Check for the unique identifiers of your expected error.
is_exit_code_35 = "Exit code: 35" in error_message
is_ssl_version_error = "wrong version number" in error_message
if is_exit_code_35 and is_ssl_version_error:
# This is the exact error you were expecting.
# print("Caught expected cURL error (Exit code 35, SSL wrong version number).")
return True
else:
# This is a different, unexpected error.
print(f"\n[cURL] An unexpected exception occurred: {e}")
return False
def main():
"""Main function to parse arguments and orchestrate tasks."""
ip_address_prefix = "10.81.56." # DK2 subnet
ip_address_range = list(range(129, 145)) # From 129 to 144 (16 CUBEs)
# ip_address_range.append(72) # Add 85 after 74.
ip_address_prefix = "10.84.165." # DK2 subnet
ip_address_range = list(range(131, 188)) # From 129 to 144 (16 CUBEs)
# ip_address_range.append(85) # Add 85 after 74.
hosts = [f"{ip_address_prefix}{suffix}" for suffix in ip_address_range]
ssh_port = 11022
@@ -459,34 +510,47 @@ def main():
for host in hosts:
print(f"{host}", end=" - ", flush=True)
#print(f"{host}", end=" - ", flush=True)
hostname = ""
result = ""
try:
activate_ssh(host)
activate_ssh(host, True)
except Exception as e:
print(f"Exception: {e}")
continue
with Connection(host=host, user=ssh_user, port=ssh_port, connect_timeout=60, connect_kwargs=connect_args) as c:
# try:
# print(f"Hostname:", end=" ", flush=True)
# result = execute_command(c, "hostname")
# print(f"{result.strip()}", end="\n", flush=True)
# hostname = str.lower(result)
# except Exception as e:
# print(f"[Hostname] Exception: {e}")
# continue
# print(f"cURL:", end=" ", flush=True)
# result = check_for_specific_curl_error(c)
# if result:
# print(f"✅", end="\n", flush=True)
# else:
# print(f"❌", end="\n", flush=True)
try:
print(f"Hostname:", end=" ", flush=True)
result = execute_command(c, "hostname")
print(f"{result.strip()}", end="\n", flush=True)
print(f"{host};{result.strip()}", end=";", flush=True)
hostname = str.lower(result)
except Exception as e:
print(f"[Hostname] Exception: {e}")
print(f"{host};ERROR")
continue
try:
print(f"cURL:", end=" ", flush=True)
result = execute_command(c, "curl -m 15 -x https://10.81.35.126:8080 https://iot-ingest-ess-prod.azure-devices.net")
print(f"{result.strip()}", end="\n", flush=True)
result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password)
print(cloud_configuration_csv(result))
except Exception as e:
print(f"[cURL] Exception: {e}")
continue
# try:
@@ -508,6 +572,35 @@ def main():
# continue
# cloud_configuration_check(hostname, result, "iot-ingest-ess-prod.azure-devices.net", "10.81.35.126", "8080")
# result_telemetry_off = set_config_field(result, "telemetry-on", False)
# result = result_telemetry_off
# cloud_configuration_check(hostname, result, "iot-ingest-ess-prod.azure-devices.net", "10.81.35.126", "8080")
# try:
# write_remote_config_sudo(c, "/etc/cube/config-azure.properties", result, ssh_password, "cube", "root", "644")
# print(f"✅", end="\n", flush=True)
# except Exception as e:
# print(f"❌", end="\n", flush=True)
# print(f"[Proxy configuration] Exception: {e}")
# continue
# print(f"Checking Cloud configuration:", end=" ", flush=True)
# try:
# result = read_remote_config_sudo(c, "/etc/cube/config-azure.properties", ssh_password)
# print(f"✅", end="\n", flush=True)
# except Exception as e:
# print(f"❌", end="\n", flush=True)
# print(f"[Proxy verification] Exception: {e}")
# continue
# try:
# print(f"Restarting cube-web-cloudagent: ", end=" ", flush=True)
# execute_sudo_command(c, "systemctl restart cube-web-cloudagent", ssh_password)
# print(f"✅", end="\n", flush=True)
# except Exception as e:
# print(f"❌", end="\n", flush=True)
# print(f"[Restarting cube-web-cloudagent] Exception: {e}")
# continue
# print(f"Setting proxy configuration:", end="\n", flush=True)
# result_proxy_host = set_config_field(result, "proxy-host", "10.81.35.126", True)