# HG changeset patch # User Edouard Tisserant # Date 1635524403 -7200 # Node ID c3d462118d37fe05299c60fc56a2622744a220be # Parent d486b98d70055c22d5e9d88963c6868ee4e8ab57# Parent e655ec8162e1381132a9ff8eafbace9971dc8e92 merged diff -r d486b98d7005 -r c3d462118d37 BeremizIDE.py --- a/BeremizIDE.py Fri Oct 22 12:48:22 2021 +0200 +++ b/BeremizIDE.py Fri Oct 29 18:20:03 2021 +0200 @@ -274,6 +274,28 @@ self.Bind(wx.EVT_MENU, OpenExemple, item) parent.AppendSeparator() + parent.AppendMenu(wx.ID_ANY, _("&Tutorials and Examples"), self.TutorialsProjectsMenu) + + exemples_dir = Bpath("exemples") + project_list = sorted(os.listdir(exemples_dir)) + + for idx, dirname in enumerate(project_list): + text = u'&%d: %s' % (idx + 1, dirname) + + item = self.TutorialsProjectsMenu.Append(wx.ID_ANY, text, '') + + projectpath = os.path.join(exemples_dir, dirname) + + def OpenExemple(event): + if self.CTR is not None and not self.CheckSaveBeforeClosing(): + return + + self.OpenProject(projectpath) + if not self.CTR.CheckProjectPathPerm(): + self.ResetView() + + self.Bind(wx.EVT_MENU, OpenExemple, item) + parent.AppendSeparator() AppendMenu(parent, help='', id=wx.ID_SAVE, kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S') AppendMenu(parent, help='', id=wx.ID_SAVEAS, diff -r d486b98d7005 -r c3d462118d37 ProjectController.py --- a/ProjectController.py Fri Oct 22 12:48:22 2021 +0200 +++ b/ProjectController.py Fri Oct 29 18:20:03 2021 +0200 @@ -36,6 +36,7 @@ import shutil import re import tempfile +import hashlib from datetime import datetime from weakref import WeakKeyDictionary from functools import reduce @@ -277,6 +278,9 @@ self.DebugToken = None self.debug_status = PlcStatus.Stopped + self.IECcodeDigest = None + self.LastBuiltIECcodeDigest = None + def __del__(self): self.KillDebugThread() @@ -779,21 +783,27 @@ self.logger.write_error( _("Error in ST/IL/SFC code generator :\n%s\n") % errors[0]) return False - plc_file = open(self._getIECcodepath(), "w") + # Add ST Library from confnodes - plc_file.write(self.GetLibrariesSTCode()) - if os.path.isfile(self._getIECrawcodepath()): - plc_file.write(open(self._getIECrawcodepath(), "r").read()) - plc_file.write("\n") - plc_file.close() - plc_file = open(self._getIECcodepath(), "r") - self.ProgramOffset = 0 - for dummy in plc_file.readlines(): - self.ProgramOffset += 1 - plc_file.close() - plc_file = open(self._getIECcodepath(), "a") - plc_file.write(open(self._getIECgeneratedcodepath(), "r").read()) - plc_file.close() + IECCodeContent = self.GetLibrariesSTCode() + + IECrawcodepath = self._getIECrawcodepath() + if os.path.isfile(IECrawcodepath): + IECCodeContent += open(IECrawcodepath, "r").read() + "\n" + + + # Compute offset before ST resulting of transformation from user POUs + self.ProgramOffset = IECCodeContent.count("\n") + + IECCodeContent += open(self._getIECgeneratedcodepath(), "r").read() + + with open(self._getIECcodepath(), "w") as plc_file: + plc_file.write(IECCodeContent) + + hasher = hashlib.md5() + hasher.update(IECCodeContent) + self.IECcodeDigest = hasher.hexdigest() + return True def _Compile_ST_to_SoftPLC(self): @@ -802,6 +812,10 @@ self.logger.write_error(_("matiec installation is not found\n")) return False + if self.LastBuiltIECcodeDigest == self.IECcodeDigest: + self.logger.write(_("IEC program did no change, not re-compiling into C code.\n")) + return True + self.logger.write(_("Compiling IEC Program into C code...\n")) buildpath = self._getBuildPath() buildcmd = "\"%s\" %s -I \"%s\" -T \"%s\" \"%s\"" % ( @@ -889,6 +903,9 @@ self.PLCGeneratedCFiles = C_files # compute CFLAGS for plc self.plcCFLAGS = '"-I%s" -Wno-unused-function' % self.iec2c_cfg.getLibCPath() + + self.LastBuiltIECcodeDigest = self.IECcodeDigest + return True def GetBuilder(self): diff -r d486b98d7005 -r c3d462118d37 opc_ua/client.py --- a/opc_ua/client.py Fri Oct 22 12:48:22 2021 +0200 +++ b/opc_ua/client.py Fri Oct 29 18:20:03 2021 +0200 @@ -49,12 +49,15 @@ EditorType = OPCUAClientEditor def __init__(self): - self.modeldata = OPCUAClientModel() + self.modeldata = OPCUAClientModel(self.Log) filepath = self.GetFileName() if os.path.isfile(filepath): self.modeldata.LoadCSV(filepath) + def Log(self, msg): + self.GetCTRoot().logger.write(msg) + def GetModelData(self): return self.modeldata diff -r d486b98d7005 -r c3d462118d37 opc_ua/opcua_client_maker.py --- a/opc_ua/opcua_client_maker.py Fri Oct 22 12:48:22 2021 +0200 +++ b/opc_ua/opcua_client_maker.py Fri Oct 29 18:20:03 2021 +0200 @@ -99,36 +99,9 @@ def AddRow(self, value): - v = dict(zip(lstcolnames, value)) - - if type(v["IEC"]) != int: - if len(self.data) == 0: - v["IEC"] = 0 - else: - iecnums = set(zip(*self.data)[lstcolnames.index("IEC")]) - greatest = max(iecnums) - holes = set(range(greatest)) - iecnums - v["IEC"] = min(holes) if holes else greatest+1 - - if v["IdType"] not in UA_NODE_ID_types: - self.log("Unknown IdType\n".format(value)) - return - - try: - for t,n in zip(lstcoltypess, lstcolnames): - v[n] = t(v[n]) - except ValueError: - self.log("Variable {} (Id={}) has invalid type\n".format(v["Name"],v["Id"])) - return - - if len(self.data)>0 and v["Id"] in zip(*self.data)[lstcolnames.index("Id")]: - self.log("Variable {} (Id={}) already in list\n".format(v["Name"],v["Id"])) - return - - self.data.append([v[n] for n in lstcolnames]) - - # notify views - self.RowAppended() + if self.data.append(value): + # notify views + self.RowAppended() def ResetData(self): self.Reset(len(self.data)) @@ -201,7 +174,7 @@ nodes = ClientPanel.GetSelectedNodes() for node in nodes: cname = node.get_node_class().name - dname = node.get_display_name().to_string() + dname = node.get_display_name().Text if cname != "Variable": self.log("Node {} ignored (not a variable)".format(dname)) continue @@ -445,20 +418,57 @@ self.selected_models[direction].ResetData() +class OPCUAClientList(list): + def __init__(self, log = lambda m:None): + super(OPCUAClientList, self).__init__(self) + self.log = log + + def append(self, value): + v = dict(zip(lstcolnames, value)) + + if type(v["IEC"]) != int: + if len(self) == 0: + v["IEC"] = 0 + else: + iecnums = set(zip(*self)[lstcolnames.index("IEC")]) + greatest = max(iecnums) + holes = set(range(greatest)) - iecnums + v["IEC"] = min(holes) if holes else greatest+1 + + if v["IdType"] not in UA_NODE_ID_types: + self.log("Unknown IdType\n".format(value)) + return False + + try: + for t,n in zip(lstcoltypess, lstcolnames): + v[n] = t(v[n]) + except ValueError: + self.log("Variable {} (Id={}) has invalid type\n".format(v["Name"],v["Id"])) + return False + + if len(self)>0 and v["Id"] in zip(*self)[lstcolnames.index("Id")]: + self.log("Variable {} (Id={}) already in list\n".format(v["Name"],v["Id"])) + return False + + list.append(self, [v[n] for n in lstcolnames]) + + return True + class OPCUAClientModel(dict): - def __init__(self): + def __init__(self, log = lambda m:None): + super(OPCUAClientModel, self).__init__() for direction in directions: - self[direction] = list() + self[direction] = OPCUAClientList(log) def LoadCSV(self,path): with open(path, 'rb') as csvfile: reader = csv.reader(csvfile, delimiter=',', quotechar='"') buf = {direction:[] for direction, _model in self.iteritems()} + for direction, model in self.iteritems(): + self[direction][:] = [] for row in reader: direction = row[0] - buf[direction].append(row[1:]) - for direction, model in self.iteritems(): - self[direction][:] = buf[direction] + self[direction].append(row[1:]) def SaveCSV(self,path): with open(path, 'wb') as csvfile: @@ -587,7 +597,7 @@ test_sizer.AddGrowableCol(0) test_sizer.AddGrowableRow(0) - modeldata = OPCUAClientModel() + modeldata = OPCUAClientModel(print) opcuatestpanel = OPCUAClientPanel(test_panel, modeldata, print, lambda:uri) diff -r d486b98d7005 -r c3d462118d37 svghmi/svghmi.c --- a/svghmi/svghmi.c Fri Oct 22 12:48:22 2021 +0200 +++ b/svghmi/svghmi.c Fri Oct 29 18:20:03 2021 +0200 @@ -39,6 +39,8 @@ } buf_state_t; static int global_write_dirty = 0; +static long hmitree_rlock = 0; +static long hmitree_wlock = 0; typedef struct { void *ptr; @@ -46,7 +48,6 @@ uint32_t buf_index; /* publish/write/send */ - long wlock; buf_state_t wstate[MAX_CONNECTIONS]; /* zero means not subscribed */ @@ -54,7 +55,6 @@ uint16_t age_ms[MAX_CONNECTIONS]; /* retrieve/read/recv */ - long rlock; buf_state_t rstate; } hmi_tree_item_t; @@ -83,17 +83,13 @@ static int write_iterator(uint32_t index, hmi_tree_item_t *dsc) { - uint32_t session_index = 0; - int value_changed = 0; - if(AtomicCompareExchange(&dsc->wlock, 0, 1) == 0) { - void *dest_p = &wbuf[dsc->buf_index]; + { + uint32_t session_index = 0; + int value_changed = 0; + void *dest_p = NULL; void *real_value_p = NULL; - char flags = 0; - void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags); - USINT sz = __get_type_enum_size(dsc->type); - if(__Is_a_string(dsc)){ - sz = ((STRING*)visible_value_p)->len + 1; - } + void *visible_value_p = NULL; + USINT sz = 0; while(session_index < MAX_CONNECTIONS) { if(dsc->wstate[session_index] == buf_set){ /* if being subscribed */ @@ -107,13 +103,39 @@ } } - if(dsc->wstate[session_index] == buf_new /* just subscribed - or already subscribed having value change */ - || (dsc->refresh_period_ms[session_index] > 0 - && (value_changed || (value_changed=memcmp(dest_p, visible_value_p, sz))) != 0)){ - /* if not already marked/signaled, do it */ + /* variable is sample only if just subscribed + or already subscribed and having value change */ + int do_sample = 0; + int just_subscribed = dsc->wstate[session_index] == buf_new; + if(!just_subscribed){ + int already_subscribed = dsc->refresh_period_ms[session_index] > 0; + if(already_subscribed){ + if(!value_changed){ + if(!visible_value_p){ + char flags = 0; + visible_value_p = UnpackVar(dsc, &real_value_p, &flags); + if(__Is_a_string(dsc)){ + sz = ((STRING*)visible_value_p)->len + 1; + } else { + sz = __get_type_enum_size(dsc->type); + } + dest_p = &wbuf[dsc->buf_index]; + } + value_changed = memcmp(dest_p, visible_value_p, sz) != 0; + do_sample = value_changed; + }else{ + do_sample = 1; + } + } + } else { + do_sample = 1; + } + + + if(do_sample){ if(dsc->wstate[session_index] != buf_set && dsc->wstate[session_index] != buf_tosend) { - if(dsc->wstate[session_index] == buf_new || ticktime_ms > dsc->refresh_period_ms[session_index]){ + if(dsc->wstate[session_index] == buf_new \ + || ticktime_ms > dsc->refresh_period_ms[session_index]){ dsc->wstate[session_index] = buf_tosend; global_write_dirty = 1; } else { @@ -128,7 +150,6 @@ /* copy value if changed (and subscribed) */ if(value_changed) memcpy(dest_p, visible_value_p, sz); - AtomicCompareExchange(&dsc->wlock, 1, 0); } // else ... : PLC can't wait, variable will be updated next turn return 0; @@ -137,9 +158,6 @@ static uint32_t send_session_index; static int send_iterator(uint32_t index, hmi_tree_item_t *dsc) { - while(AtomicCompareExchange(&dsc->wlock, 0, 1)) - nRT_reschedule(); - if(dsc->wstate[send_session_index] == buf_tosend) { uint32_t sz = __get_type_enum_size(dsc->type); @@ -159,39 +177,29 @@ else { printf("BUG!!! %%d + %%ld + %%d > %%ld \n", sbufidx, sizeof(uint32_t), sz, sizeof(sbuf)); - AtomicCompareExchange(&dsc->wlock, 1, 0); return EOVERFLOW; } } - AtomicCompareExchange(&dsc->wlock, 1, 0); return 0; } static int read_iterator(uint32_t index, hmi_tree_item_t *dsc) { - if(AtomicCompareExchange(&dsc->rlock, 0, 1) == 0) - { - if(dsc->rstate == buf_set) - { - void *src_p = &rbuf[dsc->buf_index]; - void *real_value_p = NULL; - char flags = 0; - void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags); - memcpy(real_value_p, src_p, __get_type_enum_size(dsc->type)); - dsc->rstate = buf_free; - } - AtomicCompareExchange(&dsc->rlock, 1, 0); - } - // else ... : PLC can't wait, variable will be updated next turn + if(dsc->rstate == buf_set) + { + void *src_p = &rbuf[dsc->buf_index]; + void *real_value_p = NULL; + char flags = 0; + void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags); + memcpy(real_value_p, src_p, __get_type_enum_size(dsc->type)); + dsc->rstate = buf_free; + } return 0; } void update_refresh_period(hmi_tree_item_t *dsc, uint32_t session_index, uint16_t refresh_period_ms) { - while(AtomicCompareExchange(&dsc->wlock, 0, 1)) - nRT_reschedule(); - if(refresh_period_ms) { if(!dsc->refresh_period_ms[session_index]) { @@ -201,7 +209,6 @@ dsc->wstate[session_index] = buf_free; } dsc->refresh_period_ms[session_index] = refresh_period_ms; - AtomicCompareExchange(&dsc->wlock, 1, 0); } static uint32_t reset_session_index; @@ -250,13 +257,19 @@ void __retrieve_svghmi() { - traverse_hmi_tree(read_iterator); + if(AtomicCompareExchange(&hmitree_rlock, 0, 1) == 0) { + traverse_hmi_tree(read_iterator); + AtomicCompareExchange(&hmitree_rlock, 1, 0); + } } void __publish_svghmi() { global_write_dirty = 0; - traverse_hmi_tree(write_iterator); + if(AtomicCompareExchange(&hmitree_wlock, 0, 1) == 0) { + traverse_hmi_tree(write_iterator); + AtomicCompareExchange(&hmitree_wlock, 1, 0); + } if(global_write_dirty) { SVGHMI_WakeupFromRTThread(); } @@ -274,17 +287,25 @@ int res; sbufidx = HMI_HASH_SIZE; send_session_index = session_index; + + while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){ + nRT_reschedule(); + } + if((res = traverse_hmi_tree(send_iterator)) == 0) { if(sbufidx > HMI_HASH_SIZE){ memcpy(&sbuf[0], &hmi_hash[0], HMI_HASH_SIZE); *ptr = &sbuf[0]; *size = sbufidx; + AtomicCompareExchange(&hmitree_wlock, 1, 0); return 0; } + AtomicCompareExchange(&hmitree_wlock, 1, 0); return ENODATA; } // printf("collected BAD result %%d\n", res); + AtomicCompareExchange(&hmitree_wlock, 1, 0); return res; } else @@ -294,6 +315,7 @@ } typedef enum { + unset = -1, setval = 0, reset = 1, subscribe = 2 @@ -320,10 +342,29 @@ return -EINVAL; } + int ret; + int got_wlock = 0; + int got_rlock = 0; + cmd_from_JS cmd_old = unset; + cmd_from_JS cmd = unset; + while(cursor < end) { uint32_t progress; - cmd_from_JS cmd = *(cursor++); + + cmd_old = cmd; + cmd = *(cursor++); + + if(cmd_old != cmd){ + if(got_wlock){ + AtomicCompareExchange(&hmitree_wlock, 1, 0); + got_wlock = 0; + } + if(got_rlock){ + AtomicCompareExchange(&hmitree_rlock, 1, 0); + got_rlock = 0; + } + } switch(cmd) { case setval: @@ -350,23 +391,28 @@ if((valptr + sz) <= end) { // rescheduling spinlock until free - while(AtomicCompareExchange(&dsc->rlock, 0, 1)) - nRT_reschedule(); + if(!got_rlock){ + while(AtomicCompareExchange(&hmitree_rlock, 0, 1)){ + nRT_reschedule(); + } + got_rlock=1; + } memcpy(dst_p, valptr, sz); dsc->rstate = buf_set; - AtomicCompareExchange(&dsc->rlock, 1, 0); progress = sz + sizeof(uint32_t) /* index */; } else { - return -EINVAL; + ret = -EINVAL; + goto exit_free; } } else { - return -EINVAL; + ret = -EINVAL; + goto exit_free; } } break; @@ -375,6 +421,12 @@ { progress = 0; reset_session_index = session_index; + if(!got_wlock){ + while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){ + nRT_reschedule(); + } + got_wlock = 1; + } traverse_hmi_tree(reset_iterator); } break; @@ -386,12 +438,19 @@ if(index < HMI_ITEM_COUNT) { + if(!got_wlock){ + while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){ + nRT_reschedule(); + } + got_wlock = 1; + } hmi_tree_item_t *dsc = &hmi_tree_item[index]; update_refresh_period(dsc, session_index, refresh_period_ms); } else { - return -EINVAL; + ret = -EINVAL; + goto exit_free; } progress = sizeof(uint32_t) /* index */ + @@ -404,6 +463,17 @@ } cursor += progress; } - return was_hearbeat; -} - + ret = was_hearbeat; + +exit_free: + if(got_wlock){ + AtomicCompareExchange(&hmitree_wlock, 1, 0); + got_wlock = 0; + } + if(got_rlock){ + AtomicCompareExchange(&hmitree_rlock, 1, 0); + got_rlock = 0; + } + return ret; +} + diff -r d486b98d7005 -r c3d462118d37 svghmi/svghmi.py --- a/svghmi/svghmi.py Fri Oct 22 12:48:22 2021 +0200 +++ b/svghmi/svghmi.py Fri Oct 29 18:20:03 2021 +0200 @@ -523,7 +523,7 @@ build_path = self._getBuildPath() target_path = os.path.join(build_path, target_fname) - hash_path = os.path.join(build_path, "svghmi.md5") + hash_path = os.path.join(build_path, "svghmi_"+location_str+".md5") self.GetCTRoot().logger.write("SVGHMI:\n") @@ -608,6 +608,9 @@ """) target_file.close() + # In case no SVG is given, watchdog is useless + svghmi_options["enable_watchdog"] = False + res += ((target_fname, open(target_path, "rb")),) svghmi_cmds = {}