Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
573 changes: 573 additions & 0 deletions CERTIFICATE_ROTATION_IMPLEMENTATION.md

Large diffs are not rendered by default.

20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- Author: Softweb Solutions An Avnet Company
- Brief: IOT Connect SDK: Software Development Kit 1.0
- Modify: 15-April-2024
- Modify: 24-Sep-2024

This IoTConnect Python SDK works on IoTConnect Message version 2.1, you can find more details on message version form [here](https://docs.iotconnect.io/iotconnect/sdk/message-protocol/device-message-2-1/). Below is step by step guid to help you install sdk and run sample.

Expand All @@ -9,7 +9,7 @@ Getting started

Prerequisites
Before you install and run the firmware file, we recommend to check with the following set up requirements:
1. Python: IoTConnect's Python SDK supports 2.7, 3.5, 3.7 to 3.9 and 3.10 Python versions. However, we suggest to install the most stable Python version 3.10.0
1. Python: IoTConnect's Python SDK supports 2.7, 3.5, 3.7 to 3.12 and 3.13 Python versions. However, we suggest to install the most stable Python version 3.13.0
2. pip: pip is compatible with the Python version
3. setuptools: It requires to manage the Python packages

Expand Down Expand Up @@ -110,15 +110,13 @@ To receive the command from cloud-to-device
def DeviceCallback(msg):
print(json.dumps(msg))
if cmdType == 0:
#Device comand Received
if "id" in data:
if "ack" in data and data["ack"]:
Sdk.sendAckCmd(data["ack"],7,"sucessfull",data["id"]) #fail=4,executed= 5,sucess=7,6=executedack
#To send ACK for gateway type device
else:
if "ack" in data and data["ack"]:
Sdk.sendAckCmd(data["ack"],7,"sucessfull") #fail=4,executed= 5,sucess=7,6=executedack
#To send ACK for non-gateway type device
data=msg
if data != None:
if "ack" in data and data["ack"]:
if "id" in data:
Sdk.sendAckCmd(data["ack"],2,"sucessfull",data["id"]) #Executed (Cloud Only) = 0, Failed = 1, Executed Ack = 2
else:
Sdk.sendAckCmd(data["ack"],2,"sucessfull") #Executed (Cloud Only) = 0, Failed = 1, Executed Ack = 2
```

To receive the OTA command from cloud-to-device
Expand Down
675 changes: 578 additions & 97 deletions iotconnect-sdk-1.0/iotconnect/IoTConnectSDK.py

Large diffs are not rendered by default.

97 changes: 31 additions & 66 deletions iotconnect-sdk-1.0/iotconnect/client/mqttclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import time
from iotconnect.IoTConnectSDKException import IoTConnectSDKException
import inspect

authType = {
"KEY": 1,
Expand All @@ -20,6 +21,7 @@ class mqttclient:
_name = None
_auth_type = None
_sdk_config = None
_isDebug = False
_config = None
_subTopic = None
_pubTopic = None
Expand All @@ -44,7 +46,8 @@ class mqttclient:
_path_to_root_cert=None
_onDirectMethod=None
_direct_sub="$iothub/methods/POST/#"
_direct_pub_res_topic="$iothub/methods/res/{status}/?$rid={request id}"
_direct_pub_res_topic="$iothub/methods/res/{status}/?$rid={rId}"
# _direct_pub_res_topic="$iothub/methods/res/{status}/?$rid={requestId}"
_mqtt_status = {
0: "MQTT: Connection successful",
1: "MQTT: Connection refused - incorrect protocol version",
Expand Down Expand Up @@ -118,18 +121,15 @@ def _on_message(self, client, userdatam, msg):
if msg.topic.find(self._subTopic[:-1]) > -1 and self._onMessage != None:
self._onMessage(msg_data)
if msg.topic.find(self._twin_sub_topic[:-1]) > -1 and self._onTwinMessage != None:
# print ("_twin_sub_topic")
print(msg_data)
self.print_debuglog(msg_data, 0)
self._onTwinMessage(msg_data,1)
if msg.topic.find(self._twin_sub_res_topic[:-1]) > -1:
# print ("twin_sub_res_topic")
print(msg_data)
self.print_debuglog(msg_data, 0)
self._onTwinMessage(msg_data,0)
if msg.topic.find(self._direct_sub[:-1]) > -1 and self._onDirectMethod != None:
method=str(msg.topic.replace(self._direct_sub[:-1],''))
leng=method.find('/')
method=method.replace(method[leng:],'')
#print(method)
leng=msg.topic.find("rid=")
if leng > -1:
rid=msg.topic[leng+4:]
Expand All @@ -149,8 +149,7 @@ def _connect(self):
time.sleep(0.5)

if self._rc_status == 0:
print("\n____________________\n\nProtocol Initialized\nDevice Is Connected with IoTConnect\n____________________\n")
# print("Device Is Connected with IoTConnect\n")
self.print_debuglog("Device Is Connected with IoTConnect", 0)
else:
raise(IoTConnectSDKException("06", self._mqtt_status[self._rc_status]))
except Exception as ex:
Expand Down Expand Up @@ -205,7 +204,8 @@ def Send(self,data,msgtype):
pubtopic=None
if self._isConnected:
if msgtype == "RPTEDGE":
print(data)
# print(data)
self.print_debuglog(data, 0)
pubtopic=self._pubERpt
elif msgtype == "RMEdge":
pubtopic=self._pubERm
Expand All @@ -223,6 +223,7 @@ def Send(self,data,msgtype):
pubtopic=self._pubFlt

if self._client and pubtopic != None:
self.print_debuglog(pubtopic, 0)
if pubtopic == self._pubACK:
_obj = self._client.publish(pubtopic, payload=json.dumps(data),qos=0)
return True
Expand Down Expand Up @@ -252,8 +253,7 @@ def SendTwinData(self, data):
try:
_obj = None
if self._isConnected:
# print("_twin_pub_topic")
print(data)
self.print_debuglog(data, 0)
if self._client and self._twin_pub_topic != None:
_obj = self._client.publish(self._twin_pub_topic, payload=json.dumps(data), qos=1)

Expand All @@ -268,56 +268,14 @@ def SendDirectData(self,data,status,requestId):
try:
if self._isConnected:
if self._direct_pub_res_topic:
self._direct_pub_res_topic=self._direct_pub_res_topic.replace("{status}", status)
self._direct_pub_res_topic=self._direct_pub_res_topic.replace("{request id}",requestId)
obj=self._client.publish(self._direct_pub_res_topic, payload=json.dumps(data))
#print(obj)
self._direct_pub_res_topic = self._direct_pub_res_topic.replace("{status}", status)
res_topic = self._direct_pub_res_topic.replace("{rId}",requestId)
obj=self._client.publish(res_topic, payload=json.dumps(data))
self.print_debuglog("Direct Method Data Published", 0)
self.print_debuglog(data, 0)
except:
return False

def _init_mqtt_old(self):
try:
self.Disconnect()
self._client = mqtt.Client(client_id=self._config['id'], clean_session=True, userdata=None, protocol=mqtt.MQTTv311)
#Check Auth Type
if (self._auth_type == authType["KEY"]) or (self._auth_type == authType["SKEY"]):
if self._config['pf'] == "az":
self._client.username_pw_set(self._config["un"], self._config["pwd"])
if self._path_to_root_cert != None:
self._client.tls_set(self._path_to_root_cert, tls_version = ssl.PROTOCOL_TLSv1_2)
elif (self._auth_type == authType["CA_SIGNED"] or self._auth_type == authType["CA_ind"]) :
if self._config['pf'] == "az":
self._client.username_pw_set(self._config["un"], None)
cert_setting = self._validateSSL(self._sdk_config["certificate"])
if len(cert_setting) != 3:
raise(IoTConnectSDKException("01", "Certificate/Key in Sdkoption"))
if cert_setting != None:
if self._path_to_root_cert != None:
self._client.tls_set(self._path_to_root_cert, certfile=str(cert_setting["SSLCertPath"]), keyfile=str(cert_setting["SSLKeyPath"]), cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)
else:
self._client.tls_set(str(cert_setting["SSLCaPath"]), certfile=str(cert_setting["SSLCertPath"]), keyfile=str(cert_setting["SSLKeyPath"]), cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)
self._client.tls_insecure_set(False)
elif self._auth_type == authType["CA_SELF_SIGNED"]:
if self._config['pf'] == "az":
self._client.username_pw_set(self._config["un"], None)
cert_setting = self._validateSSL(self._sdk_config["certificate"])
if len(cert_setting) != 3:
raise(IoTConnectSDKException("01", "Certificate/Key in Sdkoption"))
if cert_setting != None:
if self._path_to_root_cert != None:
self._client.tls_set(self._path_to_root_cert, certfile=str(cert_setting["SSLCertPath"]), keyfile=str(cert_setting["SSLKeyPath"]), cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)
else:
self._client.tls_set(str(cert_setting["SSLCaPath"]), certfile=str(cert_setting["SSLCertPath"]), keyfile=str(cert_setting["SSLKeyPath"]), cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)
self._client.tls_insecure_set(False)
self._client.on_connect = self._on_connect
self._client.on_disconnect = self._on_disconnect
self._client.on_message = self._on_message
self._client.disable_logger()
if self._client != None:
self._connect()
except Exception as ex:
raise ex

def is_not_blank(self, s):
return bool(s and s.strip())

Expand Down Expand Up @@ -357,6 +315,13 @@ def _init_mqtt(self):
except Exception as ex:
raise ex

def print_debuglog(self,msg,is_error):
if self._isDebug:
if is_error:
print("ERROR : {}".format(msg))
else:
print("SDK_MQTT_INFO : {}".format(msg))

@property
def isConnected(self):
return self._isConnected
Expand All @@ -365,11 +330,12 @@ def isConnected(self):
def name(self):
return self._config["n"]

def __init__(self, auth_type, config, sdk_config, onMessage,onDirectMethod,onTwinMessage = None):
def __init__(self, auth_type, config, sdk_config, onMessage, onDirectMethod, onTwinMessage = None, isDebug = None):

self._auth_type = auth_type
self._config = config
self._sdk_config = sdk_config
self._isDebug = isDebug
self._keepalive= sdk_config["keepalive"] if "keepalive" in sdk_config else 60
self._onMessage = onMessage
self._onTwinMessage = onTwinMessage
Expand All @@ -386,11 +352,10 @@ def __init__(self, auth_type, config, sdk_config, onMessage,onDirectMethod,onTwi
self._pubDL=str(config['topics']['dl'])
self._pubDi=str(config['topics']['di'])
platfrom = config["pf"]
# print (platfrom)
if config["pf"] == "az":
print ("\n============>>>>>>>>>>>\n")
print ("IoTConnect Python 2.1 SDK(Release Date: 24 December 2022) will connect with -> Microsoft Azure Cloud <-")
print ("\n<<<<<<<<<<<============\n")
# print ("\n============>>>>>>>>>>>\n")
self.print_debuglog("IoTConnect Python 2.1 SDK(Release Date: 24 December 2022) will connect with -> Microsoft Azure Cloud <-", 0)
# print ("\n<<<<<<<<<<<============\n")
self._twin_pub_topic = str(sdk_config['az']['twin_pub_topic'])
self._twin_sub_topic = str(sdk_config['az']['twin_sub_topic'])
self._twin_sub_res_topic = str(sdk_config['az']['twin_sub_res_topic'])
Expand All @@ -400,9 +365,9 @@ def __init__(self, auth_type, config, sdk_config, onMessage,onDirectMethod,onTwi
_config_path = _config_path.replace("\\client","")
self._path_to_root_cert = _config_path
else:
print ("\n============>>>>>>>>>>>\n")
print ("IoTConnect Python 2.1 SDK(Release Date: 24 December 2022) will connect with -> AWS Cloud <-")
print ("\n<<<<<<<<<<<============\n")
# print ("\n============>>>>>>>>>>>\n")
self.print_debuglog("IoTConnect Python 2.1 SDK(Release Date: 24 December 2022) will connect with -> AWS Cloud <-", 0)
# print ("\n<<<<<<<<<<<============\n")
cpid_uid = (config["id"])
self._twin_pub_topic = str(config['topics']['set']['pub'])
self._twin_sub_topic= str(config['topics']['set']['sub'])
Expand Down
4 changes: 2 additions & 2 deletions iotconnect-sdk-1.0/iotconnect/common/rule_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def evalRules(self, rule, rule_data):

sdata = {}
sdata["cv"] = cvdata
sdata["d"] = fdata
sdata["d"] = [ fdata ]
sdata["rg"] = rule["g"]
sdata["ct"] = rule["con"]
sdata["sg"] = rule["es"]
Expand Down Expand Up @@ -164,7 +164,7 @@ def evalRules_old(self, rule, rule_data):
sdata["cv"]=d

if len(sdata) > 0:
sdata["d"]=full_data
sdata["d"]= [ full_data ]
sdata["rg"] = rule["g"]
sdata["ct"] = rule["con"]
sdata["sg"] = rule["es"]
Expand Down
98 changes: 97 additions & 1 deletion iotconnect-sdk-1.0/iotconnect/common/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,5 +258,101 @@ def cert_validate(cert, auth_type):
if isvalid == True and sslCaPath and util.is_not_blank(sslCaPath) and os.path.isfile(sslCaPath) == True:
if sslCaPath.lower().endswith(".pem") != True:
isvalid = False

return isvalid

@staticmethod
def der_hex_to_pem(der_hex, cert_type="CERTIFICATE"):
"""
Convert DER hex string to PEM format

Args:
der_hex: Certificate or key in DER hex format
cert_type: Type of certificate - "CERTIFICATE", "RSA PRIVATE KEY", "PRIVATE KEY"

Returns:
PEM formatted string
"""
try:
import base64

# Convert hex string to bytes
der_bytes = bytes.fromhex(der_hex)

# Convert to base64
base64_data = base64.b64encode(der_bytes).decode('utf-8')

# Split into 64-character lines
lines = [base64_data[i:i+64] for i in range(0, len(base64_data), 64)]

# Format as PEM
pem_data = "-----BEGIN {}-----\n".format(cert_type)
pem_data += "\n".join(lines)
pem_data += "\n-----END {}-----\n".format(cert_type)

return pem_data

except Exception as ex:
print("DER hex to PEM conversion failed: " + str(ex))
return None

@staticmethod
def save_pem_file(pem_data, file_path):
"""
Save PEM data to a file

Args:
pem_data: PEM formatted string
file_path: Path to save the file

Returns:
True if successful, False otherwise
"""
try:
# Save with explicit encoding and newline handling
with open(file_path, 'w', encoding='utf-8', newline='\n') as f:
f.write(pem_data)
return True
except Exception as ex:
print("Failed to save PEM file: " + str(ex))
return False

@staticmethod
def convert_and_save_certificates(dc_hex, pk_hex, cert_path, key_path):
"""
Convert DER hex certificates to PEM and save to files

Args:
dc_hex: Device certificate in DER hex format
pk_hex: Private key in DER hex format
cert_path: Path to save the certificate file
key_path: Path to save the key file

Returns:
True if successful, False otherwise
"""
try:
# Convert device certificate
cert_pem = util.der_hex_to_pem(dc_hex, "CERTIFICATE")
if not cert_pem:
return False

# Convert private key
key_pem = util.der_hex_to_pem(pk_hex, "PRIVATE KEY")
if not key_pem:
return False

# Save certificate
if not util.save_pem_file(cert_pem, cert_path):
return False

# Save private key
if not util.save_pem_file(key_pem, key_path):
return False

print("Certificates converted and saved successfully")
return True

except Exception as ex:
print("Certificate conversion and save failed: " + str(ex))
return False
Loading