edouard@3979: # mqtt/client.py
edouard@3979: 
edouard@3979: from __future__ import absolute_import
edouard@3979: 
edouard@3979: import os
edouard@4005: import re
edouard@4023: import wx
edouard@3979: 
edouard@3979: from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
edouard@3979: from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT
edouard@3979: from .mqtt_client_gen import MQTTClientPanel, MQTTClientModel, MQTT_IEC_types, authParams
edouard@3979: 
edouard@3979: import util.paths as paths
edouard@3979: 
edouard@3984: 
edouard@3984: # assumes that "build" directory was created in paho.mqtt.c source directory,
edouard@3984: # and cmake build was invoked from this directory
edouard@3984: PahoMqttCLibraryPath = paths.ThirdPartyPath("paho.mqtt.c", "build", "src")
edouard@3984: 
edouard@4012: frozen_path = paths.ThirdPartyPath("frozen")
edouard@4012: 
edouard@4012: MqttCIncludePaths = [
edouard@3984:     paths.ThirdPartyPath("paho.mqtt.c", "build"),  # VersionInfo.h
edouard@4012:     paths.ThirdPartyPath("paho.mqtt.c", "src"),
edouard@4012:     frozen_path
edouard@3984: ]
edouard@3979: 
edouard@3979: class MQTTClientEditor(ConfTreeNodeEditor):
edouard@3979:     CONFNODEEDITOR_TABS = [
edouard@4023:         (_("MQTT Client"), "CreateMQTTClient_UI"),
edouard@4023:         (_("Info"), "CreateInfo_UI")]
edouard@3979: 
edouard@4014:     MQTTClient_UI = None
edouard@4023:     Info_UI = None
edouard@4014: 
edouard@3979:     def Log(self, msg):
edouard@3979:         self.Controler.GetCTRoot().logger.write(msg)
edouard@3979: 
edouard@3979:     def CreateMQTTClient_UI(self, parent):
edouard@4014:         self.MQTTClient_UI = MQTTClientPanel(
edouard@4010:             parent,
edouard@4010:             self.Controler.GetModelData(),
edouard@4010:             self.Log,
edouard@4010:             self.Controler.GetTypes)
edouard@4014:         return self.MQTTClient_UI
edouard@4014: 
edouard@4023:     def CreateInfo_UI(self, parent):
edouard@4023:         location_str = "_".join(map(str, self.Controler.GetCurrentLocation()))
edouard@4023:         information=("Connection status GLOBAL VAR is:\n\n\tMQTT_STATUS_"+location_str
edouard@4023:                     +", of type INT.\n\t"
edouard@4023:                     +"0 is disconnected\n\t"
edouard@4023:                     +"1 is connected\n")
edouard@4023:         self.Info_UI = wx.StaticText(parent, label = information)
edouard@4023:         return self.Info_UI
edouard@4023: 
edouard@4014:     def RefreshView(self):
edouard@4014:         if(self.MQTTClient_UI):
edouard@4014:             self.MQTTClient_UI.RefreshView()
edouard@4014:         return ConfTreeNodeEditor.RefreshView(self)
edouard@4014: 
edouard@3979: 
edouard@3979: class MQTTClient(object):
edouard@3979:     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
edouard@3979:     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
edouard@3979:       <xsd:element name="MQTTClient">
edouard@3979:         <xsd:complexType>
edouard@3979:           <xsd:sequence>
edouard@3979:             <xsd:element name="AuthType" minOccurs="0">
edouard@3979:               <xsd:complexType>
edouard@3979:                 <xsd:choice minOccurs="0">
edouard@3979:                   <xsd:element name="x509">
edouard@3979:                     <xsd:complexType>
edouard@4005:                       <xsd:attribute name="Client_certificate" type="xsd:string" use="optional" default="KeyStore.pem"/>
edouard@4005:                       <xsd:attribute name="Broker_certificate" type="xsd:string" use="optional" default="TrustStore.pem"/>
edouard@4005:                       <xsd:attribute name="Verify_hostname" type="xsd:boolean" use="optional" default="true"/>
edouard@4005:                     </xsd:complexType>
edouard@4005:                   </xsd:element>
edouard@4005:                   <xsd:element name="PSK">
edouard@4005:                     <xsd:complexType>
edouard@4005:                       <xsd:attribute name="Secret" type="xsd:string" use="optional" default=""/>
edouard@4005:                       <xsd:attribute name="ID" type="xsd:string" use="optional" default=""/>
edouard@3979:                     </xsd:complexType>
edouard@3979:                   </xsd:element>
edouard@3979:                   <xsd:element name="UserPassword">
edouard@3979:                     <xsd:complexType>
edouard@3979:                       <xsd:attribute name="User" type="xsd:string" use="optional"/>
edouard@3979:                       <xsd:attribute name="Password" type="xsd:string" use="optional"/>
edouard@3979:                     </xsd:complexType>
edouard@3979:                   </xsd:element>
edouard@3979:                 </xsd:choice>
edouard@3979:               </xsd:complexType>
edouard@3979:             </xsd:element>
edouard@3979:           </xsd:sequence>
edouard@3986:           <xsd:attribute name="Use_MQTT_5" type="xsd:boolean" use="optional" default="true"/>
edouard@3979:           <xsd:attribute name="Broker_URI" type="xsd:string" use="optional" default="ws://localhost:1883"/>
edouard@3980:           <xsd:attribute name="Client_ID" type="xsd:string" use="optional" default=""/>
edouard@3979:         </xsd:complexType>
edouard@3979:       </xsd:element>
edouard@3979:     </xsd:schema>
edouard@3979:     """
edouard@3979: 
edouard@3979:     EditorType = MQTTClientEditor
edouard@3979: 
edouard@3979:     def __init__(self):
edouard@3979:         self.modeldata = MQTTClientModel(self.Log, self.CTNMarkModified)
edouard@3979: 
edouard@3979:         filepath = self.GetFileName()
edouard@3979:         if os.path.isfile(filepath):
edouard@3979:             self.modeldata.LoadCSV(filepath)
edouard@3979: 
edouard@3979:     def Log(self, msg):
edouard@3979:         self.GetCTRoot().logger.write(msg)
edouard@3979: 
edouard@3979:     def GetModelData(self):
edouard@3979:         return self.modeldata
edouard@3979: 
edouard@4010:     def GetTypes(self):
edouard@4022:         datatype_candidates = self.GetCTRoot().GetDataTypes(basetypes=False, only_locatables=True)
edouard@4010:         return datatype_candidates
edouard@4010: 
edouard@4012:     def GetDataTypeInfos(self, typename):
edouard@4012:         tagname = "D::"+typename
edouard@4012:         return self.GetCTRoot().GetDataTypeInfos(tagname)
edouard@4012: 
edouard@3979:     def GetConfig(self):
edouard@3979:         def cfg(path): 
edouard@3979:             try:
edouard@3979:                 attr=self.GetParamsAttributes("MQTTClient."+path)
edouard@3979:             except ValueError:
edouard@3979:                 return None
edouard@3979:             return attr["value"]
edouard@3979: 
edouard@3979:         AuthType = cfg("AuthType")
edouard@3986:         res = dict(
edouard@3986:             URI=cfg("Broker_URI"),
edouard@3986:             AuthType=AuthType,
edouard@3986:             clientID=cfg("Client_ID"),
edouard@3986:             UseMQTT5=cfg("Use_MQTT_5"))
edouard@3979: 
edouard@3979:         paramList = authParams.get(AuthType, None)
edouard@3979:         if paramList:
edouard@3979:             for name,default in paramList:
edouard@4005: 
edouard@4005:                 # Translate internal config naming into user config naming
edouard@4005:                 displayed_name = {"KeyStore"   : "Client_certificate",
edouard@4005:                                   "TrustStore" : "Broker_certificate", 
edouard@4005:                                   "Verify"     : "Verify_hostname"}.get(name, name)
edouard@4005: 
edouard@4005:                 value = cfg("AuthType." + displayed_name)
edouard@3979:                 if value == "" or value is None:
edouard@3979:                     value = default
edouard@4005: 
edouard@4005:                 if value is not None:
edouard@4005:                     # cryptomaterial is expected to be in project's user provided file directory
edouard@4005: 
edouard@4005:                     # User input may contain char incompatible with C string literal escaping
edouard@4005:                     if name in ["User","Password","TrustStore","KeyStore","Broker_URI","Client_ID"]:
edouard@4005:                         value = re.sub(r'([\"\\])',  r'\\\1', value)
edouard@4005: 
edouard@3979:                 res[name] = value
edouard@3979: 
edouard@3979:         return res
edouard@3979: 
edouard@3979:     def GetFileName(self):
edouard@3979:         return os.path.join(self.CTNPath(), 'selected.csv')
edouard@3979: 
edouard@3979:     def OnCTNSave(self, from_project_path=None):
edouard@3979:         self.modeldata.SaveCSV(self.GetFileName())
edouard@3979:         return True
edouard@3979: 
edouard@3979:     def CTNGenerate_C(self, buildpath, locations):
edouard@3979:         current_location = self.GetCurrentLocation()
edouard@3979:         locstr = "_".join(map(str, current_location))
edouard@3979:         c_path = os.path.join(buildpath, "mqtt_client__%s.c" % locstr)
edouard@3979: 
edouard@3984:         c_code = """
edouard@3984: #include "iec_types_all.h"
edouard@3984: #include "beremiz.h"
edouard@3984: """
edouard@4001:         config = self.GetConfig()
edouard@4012:         c_code += self.modeldata.GenerateC(c_path, locstr, config, self.GetDataTypeInfos)
edouard@3979: 
edouard@3979:         with open(c_path, 'w') as c_file:
edouard@3979:             c_file.write(c_code)
edouard@3979: 
edouard@4001:         if config["AuthType"] == "x509":
edouard@4001:             static_lib = "libpaho-mqtt3cs.a"
edouard@4001:             libs = ['-lssl', '-lcrypto']
edouard@4001:         else:
edouard@4001:             static_lib = "libpaho-mqtt3c.a"
edouard@4001:             libs = []
edouard@4001: 
edouard@4001:         LDFLAGS = [' "' + os.path.join(PahoMqttCLibraryPath, static_lib) + '"'] + libs
edouard@3979: 
edouard@4012:         CFLAGS = ' '.join(['-I"' + path + '"' for path in MqttCIncludePaths])
edouard@4012: 
edouard@4012:         # TODO: add frozen only if using JSON
edouard@4012:         frozen_c_path = os.path.join(frozen_path, "frozen.c")
edouard@4012: 
edouard@4012:         return [(c_path, CFLAGS), (frozen_c_path, CFLAGS)], LDFLAGS, True
edouard@3979: 
edouard@3979:     def GetVariableLocationTree(self):
edouard@3979:         current_location = self.GetCurrentLocation()
edouard@3979:         locstr = "_".join(map(str, current_location))
edouard@3979:         name = self.BaseParams.getName()
edouard@3991: 
edouard@3979:         entries = []
edouard@3991:         children = []
edouard@3991: 
edouard@3991:         for row in self.modeldata["output"]:
edouard@3991:             Topic, QoS, _Retained, iec_type, iec_number = row
edouard@3991:             entries.append((Topic, QoS, iec_type, iec_number, "Q", LOCATION_VAR_OUTPUT))
edouard@3991: 
edouard@3991:         for row in self.modeldata["input"]:
edouard@3991:             Topic, QoS, iec_type, iec_number = row
edouard@3991:             entries.append((Topic, QoS, iec_type, iec_number, "I", LOCATION_VAR_INPUT))
edouard@3991: 
edouard@3991:         for Topic, QoS, iec_type, iec_number, iec_dir_prefix, loc_type in entries:
edouard@4009:             _C_type, iec_size_prefix = MQTT_IEC_types.get(iec_type,(None,""))
edouard@3991:             c_loc_name = "__" + iec_dir_prefix + iec_size_prefix + locstr + "_" + str(iec_number)
edouard@3991:             children.append({
edouard@3991:                 "name": Topic,
edouard@3991:                 "type": loc_type,
edouard@4009:                 "size": {"X":1, "B":8, "W":16, "D":32, "L":64, "":None}[iec_size_prefix],
edouard@3991:                 "IEC_type": iec_type,
edouard@3991:                 "var_name": c_loc_name,
edouard@3991:                 "location": "%" + iec_dir_prefix + iec_size_prefix + ".".join([str(i) for i in current_location]) + "." + str(iec_number),
edouard@3991:                 "description": "",
edouard@3991:                 "children": []})
edouard@3991: 
edouard@3979:         return {"name": name,
edouard@3979:                 "type": LOCATION_CONFNODE,
edouard@3979:                 "location": ".".join([str(i) for i in current_location]) + ".x",
edouard@3991:                 "children": children}
edouard@3979: 
edouard@4010: 
edouard@4010:     def CTNGlobalInstances(self):
edouard@4010:         location_str = "_".join(map(str, self.GetCurrentLocation()))
edouard@4023:         return [("MQTT_STATUS_"+location_str, "INT", ""),
edouard@4023:                ]
edouard@4023: 
edouard@4023: