edouard@3979: from __future__ import print_function edouard@3979: from __future__ import absolute_import edouard@3979: edouard@3979: import csv edouard@3979: import functools edouard@3979: from threading import Thread edouard@3984: from collections import OrderedDict edouard@3979: edouard@3979: import wx edouard@3979: import wx.dataview as dv edouard@3979: edouard@3984: # from perfect_hash import generate_code, IntSaltHash edouard@3979: edouard@3979: MQTT_IEC_types = dict( edouard@3979: # IEC61131| C type | sz edouard@3979: BOOL = ("uint8_t" , "X"), edouard@3979: SINT = ("int8_t" , "B"), edouard@3979: USINT = ("uint8_t" , "B"), edouard@3979: INT = ("int16_t" , "W"), edouard@3979: UINT = ("uint16_t", "W"), edouard@3979: DINT = ("uint32_t", "D"), edouard@3979: UDINT = ("int32_t" , "D"), edouard@3979: LINT = ("int64_t" , "L"), edouard@3979: ULINT = ("uint64_t", "L"), edouard@3979: REAL = ("float" , "D"), edouard@3979: LREAL = ("double" , "L"), edouard@3979: ) edouard@3979: edouard@3979: """ edouard@3979: QoS - Quality of Service edouard@3979: 0 : "At most one delivery" edouard@3979: 1 : "At least one delivery" edouard@3979: 2 : "Exactly one delivery" edouard@3979: """ edouard@3979: QoS_values = [0, 1, 2] edouard@3979: edouard@3980: def boolean(v): edouard@3980: if v in ["False","0"]: edouard@3980: return False edouard@3980: else: edouard@3980: return bool(v) edouard@3980: edouard@3987: lstcolnames = [ "Topic", "QoS", "Retained", "Type", "Location"] edouard@3987: lstcolwidths = [ 100, 50, 100, 100, 50] edouard@3987: lstcoltypess = [ str, int, boolean, str, int] edouard@3987: lstcoldeflts = [ "a/b/c", "1", False, "DINT", "0"] edouard@3979: Location_column = lstcolnames.index("Location") edouard@3979: edouard@3979: directions = ["input", "output"] edouard@3979: edouard@3979: authParams = { edouard@3979: "x509":[ edouard@3979: ("Certificate", "certificate.der"), edouard@3979: ("PrivateKey", "private_key.pem")], edouard@3979: "UserPassword":[ edouard@3979: ("User", None), edouard@3979: ("Password", None)]} edouard@3979: edouard@3980: class MQTTTopicListModel(dv.PyDataViewIndexListModel): edouard@3979: def __init__(self, data, log): edouard@3979: dv.PyDataViewIndexListModel.__init__(self, len(data)) edouard@3979: self.data = data edouard@3979: self.log = log edouard@3979: edouard@3979: def GetColumnType(self, col): edouard@3979: return "string" edouard@3979: edouard@3979: def GetValueByRow(self, row, col): edouard@3979: return str(self.data[row][col]) edouard@3979: edouard@3979: # This method is called when the user edits a data item in the view. edouard@3979: def SetValueByRow(self, value, row, col): edouard@3979: expectedtype = lstcoltypess[col] edouard@3979: edouard@3979: try: edouard@3979: v = expectedtype(value) edouard@3979: except ValueError: edouard@3979: self.log("String {} is invalid for type {}\n".format(value,expectedtype.__name__)) edouard@3979: return False edouard@3979: edouard@3979: if col == lstcolnames.index("QoS") and v not in QoS_values: edouard@3979: self.log("{} is invalid for IdType\n".format(value)) edouard@3979: return False edouard@3979: edouard@3979: self.data[row][col] = v edouard@3979: return True edouard@3979: edouard@3979: # Report how many columns this model provides data for. edouard@3979: def GetColumnCount(self): edouard@3979: return len(lstcolnames) edouard@3979: edouard@3979: # Report the number of rows in the model edouard@3979: def GetCount(self): edouard@3979: #self.log.write('GetCount') edouard@3979: return len(self.data) edouard@3979: edouard@3979: # Called to check if non-standard attributes should be used in the edouard@3979: # cell at (row, col) edouard@3979: def GetAttrByRow(self, row, col, attr): edouard@3979: if col == Location_column: edouard@3979: attr.SetColour('blue') edouard@3979: attr.SetBold(True) edouard@3979: return True edouard@3979: return False edouard@3979: edouard@3979: edouard@3979: def DeleteRows(self, rows): edouard@3979: # make a copy since we'll be sorting(mutating) the list edouard@3979: # use reverse order so the indexes don't change as we remove items edouard@3979: rows = sorted(rows, reverse=True) edouard@3979: edouard@3979: for row in rows: edouard@3979: # remove it from our data structure edouard@3979: del self.data[row] edouard@3979: # notify the view(s) using this model that it has been removed edouard@3979: self.RowDeleted(row) edouard@3979: edouard@3979: edouard@3979: def AddRow(self, value): edouard@3979: if self.data.append(value): edouard@3979: # notify views edouard@3979: self.RowAppended() edouard@3979: edouard@3979: def InsertDefaultRow(self, row): edouard@3979: self.data.insert(row, lstcoldeflts[:]) edouard@3979: # notify views edouard@3979: self.RowInserted(row) edouard@3979: edouard@3979: def ResetData(self): edouard@3979: self.Reset(len(self.data)) edouard@3979: edouard@3980: class MQTTTopicListPanel(wx.Panel): edouard@3979: def __init__(self, parent, log, model, direction): edouard@3979: self.log = log edouard@3979: wx.Panel.__init__(self, parent, -1) edouard@3979: edouard@3979: self.dvc = dv.DataViewCtrl(self, edouard@3979: style=wx.BORDER_THEME edouard@3979: | dv.DV_ROW_LINES edouard@3979: | dv.DV_HORIZ_RULES edouard@3979: | dv.DV_VERT_RULES edouard@3979: | dv.DV_MULTIPLE edouard@3979: ) edouard@3979: edouard@3979: self.model = model edouard@3979: edouard@3979: self.dvc.AssociateModel(self.model) edouard@3979: edouard@3979: for idx,(colname,width) in enumerate(zip(lstcolnames,lstcolwidths)): edouard@3979: self.dvc.AppendTextColumn(colname, idx, width=width, mode=dv.DATAVIEW_CELL_EDITABLE) edouard@3979: edouard@3979: edouard@3979: self.Sizer = wx.BoxSizer(wx.VERTICAL) edouard@3979: edouard@3979: self.direction = direction edouard@3979: titlestr = direction + " variables" edouard@3979: edouard@3979: title = wx.StaticText(self, label = titlestr) edouard@3979: edouard@3979: addbt = wx.Button(self, label="Add") edouard@3979: self.Bind(wx.EVT_BUTTON, self.OnAddRow, addbt) edouard@3979: delbt = wx.Button(self, label="Delete") edouard@3979: self.Bind(wx.EVT_BUTTON, self.OnDeleteRows, delbt) edouard@3979: edouard@3979: topsizer = wx.BoxSizer(wx.HORIZONTAL) edouard@3979: topsizer.Add(title, 1, wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT, 5) edouard@3979: topsizer.Add(addbt, 0, wx.LEFT|wx.RIGHT, 5) edouard@3979: topsizer.Add(delbt, 0, wx.LEFT|wx.RIGHT, 5) edouard@3979: self.Sizer.Add(topsizer, 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) edouard@3979: self.Sizer.Add(self.dvc, 1, wx.EXPAND) edouard@3979: edouard@3979: edouard@3979: def OnAddRow(self, evt): edouard@3979: items = self.dvc.GetSelections() edouard@3979: row = self.model.GetRow(items[0]) if items else 0 edouard@3979: self.model.InsertDefaultRow(row) edouard@3979: edouard@3979: def OnDeleteRows(self, evt): edouard@3979: items = self.dvc.GetSelections() edouard@3979: rows = [self.model.GetRow(item) for item in items] edouard@3979: self.model.DeleteRows(rows) edouard@3979: edouard@3979: edouard@3979: class MQTTClientPanel(wx.Panel): edouard@3979: def __init__(self, parent, modeldata, log, config_getter): edouard@3979: self.log = log edouard@3979: wx.Panel.__init__(self, parent) edouard@3979: edouard@3979: # TODO replace FlexGridSizer with a simpler one edouard@3979: self.inout_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0) edouard@3979: self.inout_sizer.AddGrowableCol(0) edouard@3979: self.inout_sizer.AddGrowableRow(0) edouard@3979: edouard@3979: self.config_getter = config_getter edouard@3979: edouard@3979: self.selected_splitter = wx.SplitterWindow(self, style=wx.SUNKEN_BORDER | wx.SP_3D) edouard@3979: edouard@3979: self.selected_datas = modeldata edouard@3980: self.selected_models = { direction:MQTTTopicListModel(self.selected_datas[direction], log) for direction in directions } edouard@3980: self.selected_lists = { direction:MQTTTopicListPanel( edouard@3979: self.selected_splitter, log, edouard@3979: self.selected_models[direction], direction) edouard@3979: for direction in directions } edouard@3979: edouard@3979: self.selected_splitter.SplitHorizontally(*[self.selected_lists[direction] for direction in directions]+[300]) edouard@3979: edouard@3979: self.inout_sizer.Add(self.selected_splitter, flag=wx.GROW) edouard@3979: self.inout_sizer.Layout() edouard@3979: self.SetAutoLayout(True) edouard@3979: self.SetSizer(self.inout_sizer) edouard@3979: edouard@3979: def OnClose(self): edouard@3979: pass edouard@3979: edouard@3979: def __del__(self): edouard@3979: self.OnClose() edouard@3979: edouard@3979: def Reset(self): edouard@3979: for direction in directions: edouard@3979: self.selected_models[direction].ResetData() edouard@3979: edouard@3979: edouard@3979: class MQTTClientList(list): edouard@3979: def __init__(self, log, change_callback): edouard@3979: super(MQTTClientList, self).__init__(self) edouard@3979: self.log = log edouard@3979: self.change_callback = change_callback edouard@3979: edouard@3979: def append(self, value): edouard@3979: v = dict(list(zip(lstcolnames, value))) edouard@3979: edouard@3979: if type(v["Location"]) != int: edouard@3979: if len(self) == 0: edouard@3979: v["Location"] = 0 edouard@3979: else: edouard@3979: iecnums = set(zip(*self)[Location_column]) edouard@3979: greatest = max(iecnums) edouard@3979: holes = set(range(greatest)) - iecnums edouard@3979: v["Location"] = min(holes) if holes else greatest+1 edouard@3979: edouard@3979: if v["QoS"] not in QoS_values: edouard@3979: self.log("Unknown QoS\n".format(value)) edouard@3979: return False edouard@3979: edouard@3979: try: edouard@3979: for t,n in zip(lstcoltypess, lstcolnames): edouard@3979: v[n] = t(v[n]) edouard@3979: except ValueError: edouard@3979: self.log("MQTT topic {} (Location={}) has invalid type\n".format(v["Topic"],v["Location"])) edouard@3979: return False edouard@3979: edouard@3979: if len(self)>0 and v["Topic"] in list(zip(*self))[lstcolnames.index("Topic")]: edouard@3979: self.log("MQTT topic {} (Location={}) already in the list\n".format(v["Topic"],v["Location"])) edouard@3979: return False edouard@3979: edouard@3979: list.append(self, [v[n] for n in lstcolnames]) edouard@3979: edouard@3979: self.change_callback() edouard@3979: edouard@3979: return True edouard@3979: edouard@3979: def __delitem__(self, index): edouard@3979: list.__delitem__(self, index) edouard@3979: self.change_callback() edouard@3979: edouard@3979: class MQTTClientModel(dict): edouard@3979: def __init__(self, log, change_callback = lambda : None): edouard@3979: super(MQTTClientModel, self).__init__() edouard@3979: for direction in directions: edouard@3979: self[direction] = MQTTClientList(log, change_callback) edouard@3979: edouard@3979: def LoadCSV(self,path): edouard@3979: with open(path, 'r') as csvfile: edouard@3979: reader = csv.reader(csvfile, delimiter=',', quotechar='"') edouard@3979: buf = {direction:[] for direction, _model in self.iteritems()} edouard@3979: for direction, model in self.iteritems(): edouard@3979: self[direction][:] = [] edouard@3979: for row in reader: edouard@3979: direction = row[0] edouard@3979: # avoids calling change callback when loading CSV edouard@3979: list.append(self[direction],row[1:]) edouard@3979: edouard@3979: def SaveCSV(self,path): edouard@3979: with open(path, 'w') as csvfile: edouard@3979: for direction, data in self.items(): edouard@3979: writer = csv.writer(csvfile, delimiter=',', edouard@3979: quotechar='"', quoting=csv.QUOTE_MINIMAL) edouard@3979: for row in data: edouard@3979: writer.writerow([direction] + row) edouard@3979: edouard@3981: def _TopicsPerfectHash(self, topics): edouard@3981: template = """ edouard@3981: #define NK $NK /* number of keys */ edouard@3981: #define NG $NG /* number of vertices */ edouard@3981: #define NS $NS /* length of array T1 and T2 */ edouard@3981: edouard@3981: int S1[] = {$S1}; edouard@3981: int S2[] = {$S2}; edouard@3981: int G[] = {$G}; edouard@3981: char *K[] = {$K}; edouard@3981: """ edouard@3984: code = generate_code(topics, Hash=IntSaltHash, template=template) edouard@3981: code += """ edouard@3981: /* return index of key in K if key is found, -1 otherwise */ edouard@3981: int MQTT_Topic_index(const char *key) edouard@3981: { edouard@3981: int i, f1 = 0, f2 = 0; edouard@3981: edouard@3981: for (i = 0; key[i] != '\\0' && i < NS; i++) { edouard@3981: f1 += S1[i] * key[i]; edouard@3981: f2 += S2[i] * key[i]; edouard@3981: f1 %= NG; edouard@3981: f2 %= NG; edouard@3981: } edouard@3981: i = (G[f1] + G[f2]) % NG; edouard@3981: if (i < NK && strcmp(key, K[i]) == 0) edouard@3981: return i; edouard@3981: edouard@3981: return -1; edouard@3981: } edouard@3981: """ edouard@3984: return code edouard@3981: edouard@3979: def GenerateC(self, path, locstr, config): edouard@3979: template = """/* code generated by beremiz MQTT extension */ edouard@3979: edouard@3984: #include edouard@3984: #include edouard@3984: edouard@3984: #include "MQTTClient.h" edouard@3980: #include "MQTTClientPersistence.h" edouard@3979: edouard@3986: #define _Log(level, ...) \\ edouard@3986: {{ \\ edouard@3984: /* char mstr[256]; */ \\ edouard@3984: /* snprintf(mstr, 255, __VA_ARGS__); */ \\ edouard@3984: /* LogMessage(level, mstr, strlen(mstr)); */ \\ edouard@3986: printf(__VA_ARGS__); \\ edouard@3979: }} edouard@3979: edouard@3979: #define LogInfo(...) _Log(LOG_INFO, __VA_ARGS__); edouard@3979: #define LogError(...) _Log(LOG_CRITICAL, __VA_ARGS__); edouard@3979: #define LogWarning(...) _Log(LOG_WARNING, __VA_ARGS__); edouard@3979: edouard@3984: void trace_callback(enum MQTTCLIENT_TRACE_LEVELS level, char* message) edouard@3984: {{ edouard@3984: LogWarning("Paho MQTT Trace : %d, %s\\n", level, message); edouard@3984: }} edouard@3984: edouard@3984: #define DECL_VAR(iec_type, C_type, c_loc_name) \\ edouard@3984: static C_type PLC_##c_loc_name##_buf = 0; \\ edouard@3984: static C_type MQTT_##c_loc_name##_buf = 0; \\ edouard@3984: C_type *c_loc_name = &PLC_##c_loc_name##_buf; edouard@3984: edouard@3984: {decl} edouard@3984: edouard@3986: static MQTTClient client; edouard@3986: #ifdef USE_MQTT_5 edouard@3986: static MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer5; edouard@3986: #else edouard@3986: static MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; edouard@3986: #endif edouard@3986: static pthread_mutex_t clientMutex; // mutex to keep PLC data consistent edouard@3986: edouard@3986: #define INIT_TOPIC(topic, iec_type, c_loc_name) \\ edouard@3984: {{#topic, &MQTT_##c_loc_name##_buf, iec_type##_ENUM}}, edouard@3984: edouard@3984: static struct {{ edouard@3984: const char *topic; //null terminated topic string edouard@3984: void *mqtt_pdata; //data from/for MQTT stack edouard@3984: __IEC_types_enum vartype; edouard@3984: }} topics [] = {{ edouard@3984: {topics} edouard@3984: }}; edouard@3984: edouard@3984: static int _connect_mqtt(void) edouard@3984: {{ edouard@3984: int rc; edouard@3986: edouard@3986: #ifdef USE_MQTT_5 edouard@3984: MQTTProperties props = MQTTProperties_initializer; edouard@3984: MQTTProperties willProps = MQTTProperties_initializer; edouard@3984: MQTTResponse response = MQTTResponse_initializer; edouard@3984: edouard@3984: response = MQTTClient_connect5(client, &conn_opts, &props, &willProps); edouard@3984: rc = response.reasonCode; edouard@3984: MQTTResponse_free(response); edouard@3986: #else edouard@3986: rc = MQTTClient_connect(client, &conn_opts); edouard@3986: #endif edouard@3984: edouard@3984: return rc; edouard@3984: }} edouard@3984: edouard@3984: void __cleanup_{locstr}(void) edouard@3984: {{ edouard@3984: int rc; edouard@3984: edouard@3984: /* TODO stop publish thread */ edouard@3984: edouard@3986: #ifdef USE_MQTT_5 edouard@3984: if (rc = MQTTClient_disconnect5(client, 5000, MQTTREASONCODE_SUCCESS, NULL) != MQTTCLIENT_SUCCESS) edouard@3986: #else edouard@3986: if (rc = MQTTClient_disconnect(client, 5000) != MQTTCLIENT_SUCCESS) edouard@3986: #endif edouard@3984: {{ edouard@3984: LogError("MQTT Failed to disconnect, return code %d\\n", rc); edouard@3979: }} edouard@3984: MQTTClient_destroy(&client); edouard@3984: }} edouard@3984: edouard@3984: void connectionLost(void* context, char* reason) edouard@3984: {{ edouard@3984: LogWarning("ConnectionLost, reconnecting\\n"); edouard@3984: _connect_mqtt(); edouard@3984: /* TODO wait if error */ edouard@3984: }} edouard@3984: edouard@3984: int messageArrived(void *context, char *topicName, int topicLen, MQTTClient_message *message) edouard@3984: {{ edouard@3984: /* TODO : something with message */ edouard@3984: printf("Message arrived\\n"); edouard@3984: printf(" topic: %s\\n", topicName); edouard@3984: printf(" message: %.*s\\n", message->payloadlen, (char*)message->payload); edouard@3984: MQTTClient_freeMessage(&message); edouard@3984: MQTTClient_free(topicName); edouard@3984: return 1; edouard@3984: }} edouard@3984: edouard@3984: #define INIT_NoAuth() \\ edouard@3986: LogInfo("MQTT Init no auth\\n"); edouard@3984: edouard@3984: #define INIT_x509(PrivateKey, Certificate) \\ edouard@3986: LogInfo("MQTT Init x509 %s,%s\\n", PrivateKey, Certificate); edouard@3984: /* TODO */ edouard@3984: edouard@3984: #define INIT_UserPassword(User, Password) \\ edouard@3986: LogInfo("MQTT Init UserPassword %s,%s\\n", User, Password); \\ edouard@3984: conn_opts.username = User; \\ edouard@3984: conn_opts.password = Password; edouard@3984: edouard@3986: #ifdef USE_MQTT_5 edouard@3987: #define _SUBSCRIBE(Topic, QoS) \\ edouard@3986: MQTTResponse response = MQTTClient_subscribe5(client, #Topic, QoS, NULL, NULL); \\ edouard@3986: rc = response.reasonCode; \\ edouard@3986: MQTTResponse_free(response); edouard@3986: #else edouard@3987: #define _SUBSCRIBE(Topic, QoS) \\ edouard@3986: rc = MQTTClient_subscribe(client, #Topic, QoS); edouard@3986: #endif edouard@3986: edouard@3984: #define INIT_SUBSCRIPTION(Topic, QoS) \\ edouard@3984: {{ \\ edouard@3987: int rc; \\ edouard@3987: _SUBSCRIBE(Topic, QoS) \\ edouard@3984: if (rc != MQTTCLIENT_SUCCESS) \\ edouard@3984: {{ \\ edouard@3986: LogError("MQTT client failed to subscribe to '%s', return code %d\\n", #Topic, rc); \\ edouard@3984: }} \\ edouard@3979: }} edouard@3979: edouard@3987: edouard@3987: edouard@3987: edouard@3987: edouard@3987: #ifdef USE_MQTT_5 edouard@3987: #define _PUBLISH(Topic, QoS, C_type, c_loc_name, Retained) \\ edouard@3987: MQTTResponse response = MQTTClient_publish5(client, #Topic, sizeof(C_type), \\ edouard@3987: &PLC_##c_loc_name##_buf, QoS, Retained, NULL, NULL); \\ edouard@3987: rc = response.reasonCode; \\ edouard@3987: MQTTResponse_free(response); edouard@3987: #else edouard@3987: #define _PUBLISH(Topic, QoS, C_type, c_loc_name, Retained) \\ edouard@3987: rc = MQTTClient_publish(client, #Topic, sizeof(C_type), \\ edouard@3987: &PLC_##c_loc_name##_buf, QoS, Retained, NULL); edouard@3987: #endif edouard@3987: edouard@3987: #define INIT_PUBLICATION(Topic, QoS, C_type, c_loc_name, Retained) \\ edouard@3987: {{ \\ edouard@3987: int rc; \\ edouard@3987: _PUBLISH(Topic, QoS, C_type, c_loc_name, Retained) \\ edouard@3987: if (rc != MQTTCLIENT_SUCCESS) \\ edouard@3987: {{ \\ edouard@3987: LogError("MQTT client failed to subscribe to '%s', return code %d\\n", #Topic, rc); \\ edouard@3987: }} \\ edouard@3987: }} edouard@3987: edouard@3987: edouard@3979: int __init_{locstr}(int argc,char **argv) edouard@3979: {{ edouard@3979: char *uri = "{uri}"; edouard@3980: char *clientID = "{clientID}"; edouard@3980: int rc; edouard@3984: edouard@3984: MQTTClient_createOptions createOpts = MQTTClient_createOptions_initializer; edouard@3984: edouard@3986: #ifdef USE_MQTT_5 edouard@3984: conn_opts.MQTTVersion = MQTTVERSION_5; edouard@3984: conn_opts.cleanstart = 1; edouard@3984: edouard@3984: createOpts.MQTTVersion = MQTTVERSION_5; edouard@3986: #else edouard@3986: conn_opts.cleansession = 1; edouard@3986: #endif edouard@3984: edouard@3984: MQTTClient_setTraceCallback(trace_callback); edouard@3984: MQTTClient_setTraceLevel(MQTTCLIENT_TRACE_ERROR); edouard@3984: edouard@3984: edouard@3984: rc = MQTTClient_createWithOptions( edouard@3984: &client, uri, clientID, MQTTCLIENT_PERSISTENCE_NONE, NULL, &createOpts); edouard@3984: if (rc != MQTTCLIENT_SUCCESS) edouard@3984: {{ edouard@3984: LogError("MQTT Failed to create client, return code %d\\n", rc); edouard@3984: return rc; edouard@3984: }} edouard@3984: edouard@3984: rc = MQTTClient_setCallbacks(client, NULL, connectionLost, messageArrived, NULL); edouard@3984: if (rc != MQTTCLIENT_SUCCESS) edouard@3984: {{ edouard@3986: LogError("MQTT Failed to set callbacks, return code %d\\n", rc); edouard@3984: return rc; edouard@3984: }} edouard@3980: edouard@3984: rc = _connect_mqtt(); edouard@3984: edouard@3984: if (rc != MQTTCLIENT_SUCCESS) {{ edouard@3986: LogError("MQTT Connect Failed, return code %d\\n", rc); edouard@3984: return rc; edouard@3979: }} edouard@3986: edouard@3986: {init} edouard@3986: edouard@3984: /* TODO start publish thread */ edouard@3984: edouard@3979: return 0; edouard@3979: }} edouard@3979: edouard@3984: #define READ_VALUE(c_loc_name, C_type) \\ edouard@3984: PLC_##c_loc_name##_buf = MQTT_##c_loc_name##_buf; edouard@3979: edouard@3979: void __retrieve_{locstr}(void) edouard@3979: {{ edouard@3984: /* TODO try take mutex */ edouard@3979: {retrieve} edouard@3984: /* TODO free mutex */ edouard@3984: }} edouard@3984: edouard@3984: #define WRITE_VALUE(c_loc_name, C_type) \\ edouard@3984: MQTT_##c_loc_name##_buf = PLC_##c_loc_name##_buf; edouard@3979: edouard@3979: void __publish_{locstr}(void) edouard@3979: {{ edouard@3984: /* TODO try take mutex */ edouard@3979: {publish} edouard@3984: /* TODO free mutex */ edouard@3984: /* TODO unblock publish thread */ edouard@3979: }} edouard@3979: edouard@3979: """ edouard@3986: edouard@3979: formatdict = dict( edouard@3979: locstr = locstr, edouard@3979: uri = config["URI"], edouard@3980: clientID = config["clientID"], edouard@3979: decl = "", edouard@3981: topics = "", edouard@3979: cleanup = "", edouard@3979: init = "", edouard@3979: retrieve = "", edouard@3986: publish = "" edouard@3979: ) edouard@3979: edouard@3986: edouard@3986: # Use Config's "MQTTVersion" to switch between protocol version at build time edouard@3986: if config["UseMQTT5"]: edouard@3986: formatdict["decl"] += """ edouard@3986: #define USE_MQTT_5""".format(**config) edouard@3986: edouard@3979: AuthType = config["AuthType"] edouard@3979: if AuthType == "x509": edouard@3979: formatdict["init"] += """ edouard@3979: INIT_x509("{PrivateKey}", "{Certificate}")""".format(**config) edouard@3979: elif AuthType == "UserPassword": edouard@3979: formatdict["init"] += """ edouard@3979: INIT_UserPassword("{User}", "{Password}")""".format(**config) edouard@3979: else: edouard@3979: formatdict["init"] += """ edouard@3979: INIT_NoAuth()""" edouard@3979: edouard@3984: topics = OrderedDict() edouard@3979: for direction, data in self.items(): edouard@3979: iec_direction_prefix = {"input": "__I", "output": "__Q"}[direction] edouard@3981: for row in data: edouard@3987: Topic, QoS, _Retained, iec_type, iec_number = row edouard@3987: Retained = 1 if _Retained=="True" else 0 edouard@3981: C_type, iec_size_prefix = MQTT_IEC_types[iec_type] edouard@3981: c_loc_name = iec_direction_prefix + iec_size_prefix + locstr + "_" + str(iec_number) edouard@3981: edouard@3981: formatdict["decl"] += """ edouard@3981: DECL_VAR({iec_type}, {C_type}, {c_loc_name})""".format(**locals()) edouard@3981: edouard@3981: formatdict["topics"] += """ edouard@3984: INIT_TOPIC({Topic}, {iec_type}, {c_loc_name})""".format(**locals()) edouard@3984: edouard@3984: if direction == "input": edouard@3984: formatdict["init"] += """ edouard@3984: INIT_SUBSCRIPTION({Topic}, {QoS})""".format(**locals()) edouard@3984: formatdict["retrieve"] += """ edouard@3984: READ_VALUE({c_loc_name}, {C_type})""".format(**locals()) edouard@3984: edouard@3984: if direction == "output": edouard@3987: formatdict["init"] += """ edouard@3987: INIT_PUBLICATION({Topic}, {QoS}, {C_type}, {c_loc_name}, {Retained})""".format(**locals()) edouard@3984: formatdict["publish"] += """ edouard@3984: WRITE_VALUE({c_loc_name}, {C_type})""".format(**locals()) edouard@3979: edouard@3979: Ccode = template.format(**formatdict) edouard@3986: edouard@3979: return Ccode edouard@3979: edouard@3979: if __name__ == "__main__": edouard@3979: edouard@3979: import wx.lib.mixins.inspection as wit edouard@3979: import sys,os edouard@3979: edouard@3979: app = wit.InspectableApp() edouard@3979: edouard@3979: frame = wx.Frame(None, -1, "MQTT Client Test App", size=(800,600)) edouard@3979: edouard@3979: argc = len(sys.argv) edouard@3979: edouard@3979: config={} edouard@3980: config["URI"] = sys.argv[1] if argc>1 else "tcp://localhost:1883" edouard@3980: config["clientID"] = sys.argv[2] if argc>2 else "" edouard@3979: config["AuthType"] = None edouard@3986: config["UseMQTT5"] = True edouard@3979: edouard@3980: if argc > 3: edouard@3980: AuthType = sys.argv[3] edouard@3979: config["AuthType"] = AuthType edouard@3980: for (name, default), value in zip_longest(authParams[AuthType], sys.argv[4:]): edouard@3979: if value is None: edouard@3979: if default is None: edouard@3979: raise Exception(name+" param expected") edouard@3979: value = default edouard@3979: config[name] = value edouard@3979: edouard@3979: test_panel = wx.Panel(frame) edouard@3979: test_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0) edouard@3979: test_sizer.AddGrowableCol(0) edouard@3979: test_sizer.AddGrowableRow(0) edouard@3979: edouard@3979: modeldata = MQTTClientModel(print) edouard@3979: edouard@3979: mqtttestpanel = MQTTClientPanel(test_panel, modeldata, print, lambda:config) edouard@3979: edouard@3979: def OnGenerate(evt): edouard@3979: dlg = wx.FileDialog( edouard@3979: frame, message="Generate file as ...", defaultDir=os.getcwd(), edouard@3986: defaultFile="", edouard@3979: wildcard="C (*.c)|*.c", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT edouard@3979: ) edouard@3979: edouard@3979: if dlg.ShowModal() == wx.ID_OK: edouard@3979: path = dlg.GetPath() edouard@3979: Ccode = """ edouard@3979: /* edouard@3979: In case open62541 was built just aside beremiz, you can build this test with: edouard@3979: gcc %s -o %s \\ edouard@3979: -I ../../open62541/plugins/include/ \\ edouard@3979: -I ../../open62541/build/src_generated/ \\ edouard@3979: -I ../../open62541/include/ \\ edouard@3979: -I ../../open62541/arch/ ../../open62541/build/bin/libopen62541.a edouard@3979: */ edouard@3979: edouard@3979: """%(path, path[:-2]) + modeldata.GenerateC(path, "test", config) + """ edouard@3979: edouard@3979: int LogMessage(uint8_t level, char* buf, uint32_t size){ edouard@3979: printf("log level:%d message:'%.*s'\\n", level, size, buf); edouard@3979: }; edouard@3979: edouard@3979: int main(int argc, char *argv[]) { edouard@3979: edouard@3979: __init_test(arc,argv); edouard@3986: edouard@3979: __retrieve_test(); edouard@3986: edouard@3979: __publish_test(); edouard@3979: edouard@3979: __cleanup_test(); edouard@3979: edouard@3979: return EXIT_SUCCESS; edouard@3979: } edouard@3979: """ edouard@3979: edouard@3979: with open(path, 'w') as Cfile: edouard@3979: Cfile.write(Ccode) edouard@3979: edouard@3979: edouard@3979: dlg.Destroy() edouard@3979: edouard@3979: def OnLoad(evt): edouard@3979: dlg = wx.FileDialog( edouard@3979: frame, message="Choose a file", edouard@3979: defaultDir=os.getcwd(), edouard@3979: defaultFile="", edouard@3979: wildcard="CSV (*.csv)|*.csv", edouard@3979: style=wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST ) edouard@3979: edouard@3979: if dlg.ShowModal() == wx.ID_OK: edouard@3979: path = dlg.GetPath() edouard@3979: modeldata.LoadCSV(path) edouard@3979: mqtttestpanel.Reset() edouard@3979: edouard@3979: dlg.Destroy() edouard@3979: edouard@3979: def OnSave(evt): edouard@3979: dlg = wx.FileDialog( edouard@3979: frame, message="Save file as ...", defaultDir=os.getcwd(), edouard@3986: defaultFile="", edouard@3979: wildcard="CSV (*.csv)|*.csv", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT edouard@3979: ) edouard@3979: edouard@3979: if dlg.ShowModal() == wx.ID_OK: edouard@3979: path = dlg.GetPath() edouard@3979: modeldata.SaveCSV(path) edouard@3979: edouard@3979: dlg.Destroy() edouard@3979: edouard@3979: test_sizer.Add(mqtttestpanel, flag=wx.GROW|wx.EXPAND) edouard@3979: edouard@3979: testbt_sizer = wx.BoxSizer(wx.HORIZONTAL) edouard@3979: edouard@3979: loadbt = wx.Button(test_panel, label="Load") edouard@3979: test_panel.Bind(wx.EVT_BUTTON, OnLoad, loadbt) edouard@3979: edouard@3979: savebt = wx.Button(test_panel, label="Save") edouard@3979: test_panel.Bind(wx.EVT_BUTTON, OnSave, savebt) edouard@3979: edouard@3979: genbt = wx.Button(test_panel, label="Generate") edouard@3979: test_panel.Bind(wx.EVT_BUTTON, OnGenerate, genbt) edouard@3979: edouard@3979: testbt_sizer.Add(loadbt, 0, wx.LEFT|wx.RIGHT, 5) edouard@3979: testbt_sizer.Add(savebt, 0, wx.LEFT|wx.RIGHT, 5) edouard@3979: testbt_sizer.Add(genbt, 0, wx.LEFT|wx.RIGHT, 5) edouard@3979: edouard@3979: test_sizer.Add(testbt_sizer, flag=wx.GROW) edouard@3979: test_sizer.Layout() edouard@3979: test_panel.SetAutoLayout(True) edouard@3979: test_panel.SetSizer(test_sizer) edouard@3979: edouard@3979: def OnClose(evt): edouard@3979: mqtttestpanel.OnClose() edouard@3979: evt.Skip() edouard@3979: edouard@3979: frame.Bind(wx.EVT_CLOSE, OnClose) edouard@3979: edouard@3979: frame.Show() edouard@3979: edouard@3979: app.MainLoop()