--- 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 = {}