merged wxPython4
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Fri, 29 Oct 2021 18:20:03 +0200
branchwxPython4
changeset 3379 c3d462118d37
parent 3371 d486b98d7005 (current diff)
parent 3378 e655ec8162e1 (diff)
child 3388 6cde1a3ded58
merged
BeremizIDE.py
IDEFrame.py
opc_ua/opcua_client_maker.py
svghmi/svghmi.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,
--- 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):
--- 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
     
--- 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)
 
--- 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;
+}
+
--- 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 = {}