--- a/ProjectController.py Tue Nov 30 18:43:10 2021 +0100
+++ b/ProjectController.py Sun Jan 16 17:00:58 2022 +0100
@@ -276,6 +276,7 @@
# copy StatusMethods so that it can be later customized
self.StatusMethods = [dic.copy() for dic in self.StatusMethods]
+ self.LastComplainDebugToken = None self.debug_status = PlcStatus.Stopped
self.IECcodeDigest = None
@@ -797,7 +798,12 @@
IECCodeContent += open(self._getIECgeneratedcodepath(), "r").read()
- with open(self._getIECcodepath(), "w") as plc_file:
+ IECcodepath = self._getIECcodepath() + if not os.path.exists(IECcodepath): + self.LastBuiltIECcodeDigest = None + with open(IECcodepath, "w") as plc_file: plc_file.write(IECCodeContent)
@@ -966,7 +972,7 @@
ProgramsListAttributeName = ["num", "C_path", "type"]
VariablesListAttributeName = [
- "num", "vartype", "IEC_path", "C_path", "type", "derived"]
+ "num", "vartype", "IEC_path", "C_path", "type", "derived", "retain"] self._DbgVariablesList = []
@@ -1047,10 +1053,9 @@
- for v in self._DbgVariablesList:
- sz = DebugTypesSize.get(v["type"], 0)
- variable_decl_array += [
+ for i, v in enumerate(self._DbgVariablesList): + variable_decl_array.append( "EXT": "%(type)s_P_ENUM",
@@ -1059,10 +1064,12 @@
"OUT": "%(type)s_O_ENUM",
+ retain_indexes.append("/* "+v["C_path"]+" */ "+str(i)) debug_code = targets.GetCode("plc_debug.c") % {
"programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" %
p for p in self._ProgramList]),
"extern_variables_declarations": "\n".join([
@@ -1076,6 +1083,7 @@
for v in self._VariablesList if v["C_path"].find('.') < 0]),
"variable_decl_array": ",\n".join(variable_decl_array),
+ "retain_vardsc_index_array": ",\n".join(retain_indexes), "var_access_code": targets.GetCode("var_access.c")
@@ -1550,6 +1558,14 @@
values_buffer.append((value, forced))
self.DebugTicks.append(debug_tick)
+ # complain if trace is incomplete, but only once per debug session + if self.LastComplainDebugToken != self.DebugToken : + self.logger.write_warning( + _("Debug: target couldn't trace all requested variables.\n")) + self.LastComplainDebugToken = self.DebugToken buffers, self.DebugValuesBuffers = (self.DebugValuesBuffers,
[list() for dummy in xrange(len(self.TracedIECPath))])
@@ -1558,6 +1574,15 @@
return debug_status, ticks, buffers
+ RegisterDebugVariableErrorCodes = { + 1 : _("Debug: Too many variables traced. Max 1024.\n"), + 2 : _("Debug: Too many variables forced. Max 256.\n"), + # FORCE_BUFFER_OVERFLOW + 3 : _("Debug: Cumulated forced variables size too large. Max 1KB.\n") def RegisterDebugVarToConnector(self):
@@ -1591,7 +1616,14 @@
self.TracedIECPath = IdxsT[3]
self.TracedIECTypes = IdxsT[1]
- self.DebugToken = self._connector.SetTraceVariablesList(zip(*IdxsT[0:3]))
+ res = self._connector.SetTraceVariablesList(zip(*IdxsT[0:3])) + if res is not None and res > 0: + self.logger.write_warning( + self.RegisterDebugVariableErrorCodes.get( + -res, _("Debug: Unknown error"))) self._connector.SetTraceVariablesList([])
--- a/runtime/PLCObject.py Tue Nov 30 18:43:10 2021 +0100
+++ b/runtime/PLCObject.py Sun Jan 16 17:00:58 2022 +0100
@@ -223,7 +223,7 @@
self._ResetDebugVariables.restype = None
self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable
- self._RegisterDebugVariable.restype = None
+ self._RegisterDebugVariable.restype = ctypes.c_int self._RegisterDebugVariable.argtypes = [ctypes.c_int, ctypes.c_void_p]
self._FreeDebugData = self.PLClibraryHandle.FreeDebugData
@@ -294,7 +294,7 @@
self._startPLC = lambda x, y: None
self._stopPLC = lambda: None
self._ResetDebugVariables = lambda: None
- self._RegisterDebugVariable = lambda x, y: None
+ self._RegisterDebugVariable = lambda x, y: 0 self._IterDebugData = lambda x, y: None
self._FreeDebugData = lambda: None
self._GetDebugData = lambda: -1
@@ -720,7 +720,11 @@
TypeTranslator.get(iectype,
force = ctypes.byref(pack_func(c_type, force))
- self._RegisterDebugVariable(idx, force)
+ res = self._RegisterDebugVariable(idx, force) + self._suspendDebug(True) --- a/svghmi/detachable_pages.ysl2 Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/detachable_pages.ysl2 Sun Jan 16 17:00:58 2022 +0100
@@ -88,7 +88,7 @@
const "required_page_elements",
"func:required_elements($hmi_pages | $keypads)/ancestor-or-self::svg:*";
-const "required_list_elements", "func:refered_elements(($hmi_lists | $hmi_textlists)[@id = $required_page_elements/@id])";
+const "required_list_elements", "func:refered_elements(($hmi_lists | $hmi_textlists)[@id = $required_page_elements/@id])/ancestor-or-self::svg:*"; const "required_elements", "$defs | $required_list_elements | $required_page_elements";
@@ -169,6 +169,7 @@
if "count($desc/path/@index)=0"
warning > Page id="«$page/@id»" : No match for path "«$desc/path/@value»" in HMI tree
| page_index: «$desc/path/@index»,
+ | page_class: "«$indexed_hmitree/*[@hmipath = $desc/path/@value]/@class»", foreach "$page_managed_widgets" {
--- a/svghmi/gen_index_xhtml.xslt Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/gen_index_xhtml.xslt Sun Jan 16 17:00:58 2022 +0100
@@ -3,6 +3,7 @@
<xsl:output cdata-section-elements="xhtml:script" method="xml"/>
<xsl:variable name="svg" select="/svg:svg"/>
<xsl:variable name="hmi_elements" select="//svg:*[starts-with(@inkscape:label, 'HMI:')]"/>
+ <xsl:param name="instance_name"/> <xsl:variable name="hmitree" select="ns:GetHMITree()"/>
<xsl:variable name="_categories">
@@ -39,12 +40,16 @@
+ <xsl:text>var current_page_var_index = </xsl:text> + <xsl:value-of select="$indexed_hmitree/*[@hmipath = concat('/CURRENT_PAGE_', $instance_name)]/@index"/> <xsl:text>var hmitree_types = [
<xsl:for-each select="$indexed_hmitree/*">
- <xsl:text> /* </xsl:text>
- <xsl:value-of select="@index"/>
- <xsl:text> */ "</xsl:text>
+ <xsl:text> "</xsl:text> <xsl:value-of select="substring(local-name(), 5)"/>
<xsl:if test="position()!=last()">
@@ -60,9 +65,7 @@
<xsl:text>var hmitree_paths = [
<xsl:for-each select="$indexed_hmitree/*">
- <xsl:text> /* </xsl:text>
- <xsl:value-of select="@index"/>
- <xsl:text> */ "</xsl:text>
+ <xsl:text> "</xsl:text> <xsl:value-of select="@hmipath"/>
<xsl:if test="position()!=last()">
@@ -75,6 +78,26 @@
+ <xsl:text>var hmitree_nodes = { + <xsl:for-each select="$indexed_hmitree/*[local-name() = 'HMI_NODE']"> + <xsl:text> "</xsl:text> + <xsl:value-of select="@hmipath"/> + <xsl:text>" : [</xsl:text> + <xsl:value-of select="@index"/> + <xsl:text>, "</xsl:text> + <xsl:value-of select="@class"/> + <xsl:text>"]</xsl:text> + <xsl:if test="position()!=last()"> @@ -549,7 +572,7 @@
<xsl:variable name="required_page_elements" select="func:required_elements($hmi_pages | $keypads)/ancestor-or-self::svg:*"/>
- <xsl:variable name="required_list_elements" select="func:refered_elements(($hmi_lists | $hmi_textlists)[@id = $required_page_elements/@id])"/>
+ <xsl:variable name="required_list_elements" select="func:refered_elements(($hmi_lists | $hmi_textlists)[@id = $required_page_elements/@id])/ancestor-or-self::svg:*"/> <xsl:variable name="required_elements" select="$defs | $required_list_elements | $required_page_elements"/>
<xsl:variable name="discardable_elements" select="//svg:*[not(@id = $required_elements/@id)]"/>
<func:function name="func:sumarized_elements">
@@ -656,6 +679,10 @@
<xsl:value-of select="$desc/path/@index"/>
+ <xsl:text> page_class: "</xsl:text> + <xsl:value-of select="$indexed_hmitree/*[@hmipath = $desc/path/@value]/@class"/> @@ -819,7 +846,7 @@
<xsl:variable name="targets_not_to_unlink" select="$hmi_lists/descendant-or-self::svg:*"/>
- <xsl:variable name="to_unlink" select="$hmi_elements[not(@id = $hmi_pages/@id)]/descendant-or-self::svg:use"/>
+ <xsl:variable name="to_unlink" select="$hmi_widgets/descendant-or-self::svg:use"/> <func:function name="func:is_unlinkable">
<xsl:param name="targetid"/>
<xsl:param name="eltid"/>
@@ -4556,7 +4583,7 @@
- <xsl:text>Documentation to be written. see svbghmi exemple.
+ <xsl:text>Documentation to be written. see svghmi exemple. @@ -4806,13 +4833,13 @@
<xsl:when test="count($from_list) > 0">
<xsl:text> id("</xsl:text>
<xsl:value-of select="@id"/>
- <xsl:text>").setAttribute("xlink:href",
+ <xsl:text>").href.baseVal = <xsl:text> "#"+hmi_widgets["</xsl:text>
<xsl:value-of select="$from_list/@id"/>
<xsl:text>"].items[</xsl:text>
<xsl:value-of select="$expressions/expression[1]/@content"/>
@@ -5648,9 +5675,9 @@
<xsl:for-each select="$hmi_element/*[@inkscape:label]">
+ <xsl:text> "</xsl:text> <xsl:value-of select="@inkscape:label"/>
- <xsl:text>: "</xsl:text>
+ <xsl:text>": "</xsl:text> <xsl:value-of select="@id"/>
@@ -5658,6 +5685,60 @@
+ <xsl:template match="widget[@type='ListSwitch']" mode="widget_desc"> + <xsl:value-of select="@type"/> + <xsl:text>ListSwitch widget displays one item of an HMI:List depending on value of + <xsl:text>given variable. Main element of the widget must be a clone of the list or + <xsl:text>of an item of that list. + <xsl:text>Given variable's current value is compared to list items + <xsl:text>label. For exemple if given variable type + <xsl:text>is HMI_INT and value is 1, then item with label '1' will be displayed. + <xsl:text>If matching variable of type HMI_STRING, then no quotes are needed. + <xsl:text>For exemple, 'hello' match HMI_STRING 'hello'. + <xsl:text>Displays item of an HMI:List whose label matches value.</xsl:text> + <path name="value" accepts="HMI_INT,HMI_STRING"> + <xsl:text>value to compare to labels</xsl:text> + <xsl:template match="widget[@type='ListSwitch']" mode="widget_class"> + <xsl:text>class </xsl:text> + <xsl:text>ListSwitchWidget</xsl:text> + <xsl:text> extends Widget{ + <xsl:text> frequency = 5; + <xsl:template match="widget[@type='ListSwitch']" mode="widget_defs"> + <xsl:param name="hmi_element"/> + <xsl:variable name="targetid" select="substring-after($hmi_element/@xlink:href,'#')"/> + <xsl:variable name="from_list" select="$hmi_lists[(@id | */@id) = $targetid]"/> + <xsl:text> dispatch: function(value) { + <xsl:text> this.element.href.baseVal = "#"+hmi_widgets["</xsl:text> + <xsl:value-of select="$from_list/@id"/> + <xsl:text>"].items[value]; <xsl:template match="widget[@type='Meter']" mode="widget_desc">
<xsl:value-of select="@type"/>
@@ -7212,7 +7293,7 @@
- <xsl:text>Show elements whose label match value.</xsl:text>
+ <xsl:text>Show elements whose label matches value.</xsl:text> <path name="value" accepts="HMI_INT,HMI_STRING">
<xsl:text>value to compare to labels</xsl:text>
@@ -8045,17 +8126,17 @@
<xsl:text>// Open WebSocket to relative "/ws" address
+ <xsl:text>var has_watchdog = window.location.hash == "#watchdog"; <xsl:text> window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
- <xsl:text> + '?mode=' + (window.location.hash == "#watchdog"
- <xsl:text> ? "watchdog"
- <xsl:text> : "multiclient");
+ <xsl:text> + '?mode=' + (has_watchdog ? "watchdog" : "multiclient"); <xsl:text>var ws = new WebSocket(ws_url);
@@ -8375,23 +8456,49 @@
- <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
- <xsl:text>// Since dispatch directly calls change_hmi_value,
- <xsl:text>// PLC will periodically send variable at given frequency
- <xsl:text>subscribers(heartbeat_index).add({
- <xsl:text> /* type: "Watchdog", */
+ <xsl:text>if(has_watchdog){ + <xsl:text> // artificially subscribe the watchdog widget to "/heartbeat" hmi variable + <xsl:text> // Since dispatch directly calls change_hmi_value, + <xsl:text> // PLC will periodically send variable at given frequency + <xsl:text> subscribers(heartbeat_index).add({ + <xsl:text> /* type: "Watchdog", */ + <xsl:text> frequency: 1, + <xsl:text> indexes: [heartbeat_index], + <xsl:text> new_hmi_value: function(index, value, oldval) { + <xsl:text> apply_hmi_value(heartbeat_index, value+1); + <xsl:text>// subscribe to per instance current page hmi variable + <xsl:text>// PLC must prefix page name with "!" for page switch to happen + <xsl:text>subscribers(current_page_var_index).add({ - <xsl:text> indexes: [heartbeat_index],
+ <xsl:text> indexes: [current_page_var_index], <xsl:text> new_hmi_value: function(index, value, oldval) {
- <xsl:text> apply_hmi_value(heartbeat_index, value+1);
+ <xsl:text> if(value.startsWith("!")) + <xsl:text> switch_page(value.slice(1)); @@ -8787,7 +8894,11 @@
<xsl:text> page_name = current_subscribed_page;
+ <xsl:text> else if(page_index == undefined){ + <xsl:text> [page_name, page_index] = page_name.split('@') @@ -8807,10 +8918,32 @@
- <xsl:text> if(page_index == undefined){
+ <xsl:text> if(page_index == undefined) <xsl:text> page_index = new_desc.page_index;
+ <xsl:text> else if(typeof(page_index) == "string") { + <xsl:text> let hmitree_node = hmitree_nodes[page_index]; + <xsl:text> if(hmitree_node !== undefined){ + <xsl:text> let [int_index, hmiclass] = hmitree_node; + <xsl:text> if(hmiclass == new_desc.page_class) + <xsl:text> page_index = int_index; + <xsl:text> page_index = new_desc.page_index; + <xsl:text> page_index = new_desc.page_index; @@ -8871,6 +9004,14 @@
+ <xsl:text> apply_hmi_value(current_page_var_index, page_index == undefined + <xsl:text> : page_name + "@" + hmitree_paths[page_index]); --- a/svghmi/hmi_tree.ysl2 Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/hmi_tree.ysl2 Sun Jan 16 17:00:58 2022 +0100
@@ -1,5 +1,7 @@
+// Location identifies uniquely SVGHMI instance // HMI Tree computed from VARIABLES.CSV in svghmi.py
const "hmitree", "ns:GetHMITree()";
@@ -19,20 +21,28 @@
| var heartbeat_index = «$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index»;
+ | var current_page_var_index = «$indexed_hmitree/*[@hmipath = concat('/CURRENT_PAGE_', $instance_name)]/@index»; foreach "$indexed_hmitree/*"
- | /* «@index» */ "«substring(local-name(), 5)»"`if "position()!=last()" > ,`
+ | "«substring(local-name(), 5)»"`if "position()!=last()" > ,` foreach "$indexed_hmitree/*"
- | /* «@index» */ "«@hmipath»"`if "position()!=last()" > ,`
+ | "«@hmipath»"`if "position()!=last()" > ,` + | var hmitree_nodes = { + foreach "$indexed_hmitree/*[local-name() = 'HMI_NODE']" + | "«@hmipath»" : [«@index», "«@class»"]`if "position()!=last()" > ,` template "*", mode="index" {
--- a/svghmi/inline_svg.ysl2 Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/inline_svg.ysl2 Sun Jan 16 17:00:58 2022 +0100
@@ -56,7 +56,7 @@
// TODO: narrow application of clone unlinking to active elements,
// while keeping static decoration cloned
const "targets_not_to_unlink", "$hmi_lists/descendant-or-self::svg:*";
-const "to_unlink", "$hmi_elements[not(@id = $hmi_pages/@id)]/descendant-or-self::svg:use";
+const "to_unlink", "$hmi_widgets/descendant-or-self::svg:use"; def "func:is_unlinkable" {
@@ -174,7 +174,7 @@
// node recursive copy ends when finding a widget
- when "@id = $hmi_elements/@id" {
+ when "@id = $hmi_widgets/@id" { // place a clone instead of copying
attrib "xlink:href" > «concat('#',@id)»
--- a/svghmi/svghmi.c Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/svghmi.c Sun Jan 16 17:00:58 2022 +0100
@@ -10,6 +10,7 @@
#define HMI_ITEM_COUNT %(item_count)d
#define MAX_CONNECTIONS %(max_connections)d
+#define MAX_CON_INDEX MAX_CONNECTIONS - 1 static uint8_t hmi_hash[HMI_HASH_SIZE] = {%(hmi_hash_ints)s};
@@ -19,7 +20,6 @@
/* PLC writes to that buffer */
static char wbuf[HMI_BUFFER_SIZE];
-/* TODO change that in case of multiclient... */
/* worst biggest send buffer. FIXME : use dynamic alloc ? */
static char sbuf[HMI_HASH_SIZE + HMI_BUFFER_SIZE + (HMI_ITEM_COUNT * sizeof(uint32_t))];
static unsigned int sbufidx;
@@ -42,11 +42,15 @@
static long hmitree_rlock = 0;
static long hmitree_wlock = 0;
+typedef struct hmi_tree_item_s hmi_tree_item_t; + /* retrieve/read/recv */ buf_state_t wstate[MAX_CONNECTIONS];
@@ -54,111 +58,114 @@
uint16_t refresh_period_ms[MAX_CONNECTIONS];
uint16_t age_ms[MAX_CONNECTIONS];
- /* retrieve/read/recv */
+ /* dual linked list for subscriptions */ + hmi_tree_item_t *subscriptions_next; + hmi_tree_item_t *subscriptions_prev;
+ /* single linked list for changes from HMI */ + hmi_tree_item_t *incoming_prev; -static hmi_tree_item_t hmi_tree_item[] = {
-typedef int(*hmi_tree_iterator)(uint32_t, hmi_tree_item_t*);
-static int traverse_hmi_tree(hmi_tree_iterator fp)
- for(i = 0; i < sizeof(hmi_tree_item)/sizeof(hmi_tree_item_t); i++){
- hmi_tree_item_t *dsc = &hmi_tree_item[i];
- int res = (*fp)(i, dsc);
+#define HMITREE_ITEM_INITIALIZER(cpath,type,buf_index) { \ + buf_index, /*buf_index*/ \ + {[0 ... MAX_CON_INDEX] = buf_free}, /*wstate*/ \ + {[0 ... MAX_CON_INDEX] = 0}, /*refresh_period_ms*/ \ + {[0 ... MAX_CON_INDEX] = 0}, /*age_ms*/ \ + NULL, /*subscriptions_next*/\ + NULL, /*subscriptions_prev*/\ + NULL} /*incoming_next*/ +/* entry for dual linked list for HMI subscriptions */ +/* points to the end of the list */ +static hmi_tree_item_t *subscriptions_tail = NULL; +/* entry for single linked list for changes from HMI */ +/* points to the end of the list */ +static hmi_tree_item_t *incoming_tail = NULL; +static hmi_tree_item_t hmi_tree_items[] = { #define __Unpack_desc_type hmi_tree_item_t
-static int write_iterator(uint32_t index, hmi_tree_item_t *dsc)
+static int write_iterator(hmi_tree_item_t *dsc)
- uint32_t session_index = 0;
- void *real_value_p = NULL;
- void *visible_value_p = NULL;
- while(session_index < MAX_CONNECTIONS) {
- if(dsc->wstate[session_index] == buf_set){
- /* if being subscribed */
- if(dsc->refresh_period_ms[session_index]){
- if(dsc->age_ms[session_index] + ticktime_ms < dsc->refresh_period_ms[session_index]){
- dsc->age_ms[session_index] += ticktime_ms;
- dsc->wstate[session_index] = buf_tosend;
- global_write_dirty = 1;
+ uint32_t session_index = 0; + while(session_index < MAX_CONNECTIONS) { + if(dsc->wstate[session_index] == buf_set){ + /* if being subscribed */ + if(dsc->refresh_period_ms[session_index]){ + if(dsc->age_ms[session_index] + ticktime_ms < dsc->refresh_period_ms[session_index]){ + dsc->age_ms[session_index] += ticktime_ms; + dsc->wstate[session_index] = buf_tosend; + global_write_dirty = 1; - /* variable is sample only if just subscribed
- or already subscribed and having value change */
- int just_subscribed = dsc->wstate[session_index] == buf_new;
- int already_subscribed = dsc->refresh_period_ms[session_index] > 0;
- if(already_subscribed){
- visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
- if(__Is_a_string(dsc)){
- sz = ((STRING*)visible_value_p)->len + 1;
- sz = __get_type_enum_size(dsc->type);
- dest_p = &wbuf[dsc->buf_index];
+ /* variable is sample only if just subscribed + or already subscribed and having value change */ + int just_subscribed = dsc->wstate[session_index] == buf_new; + int already_subscribed = dsc->refresh_period_ms[session_index] > 0; + if(already_subscribed){ + UnpackVar(dsc, &value_p, NULL, &sz); + if(__Is_a_string(dsc)){ + sz = ((STRING*)value_p)->len + 1; - value_changed = memcmp(dest_p, visible_value_p, sz) != 0;
- do_sample = value_changed;
+ dest_p = &wbuf[dsc->buf_index]; + value_changed = memcmp(dest_p, value_p, sz) != 0; + do_sample = value_changed;
- 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]){
- dsc->wstate[session_index] = buf_tosend;
- global_write_dirty = 1;
- dsc->wstate[session_index] = buf_set;
- dsc->age_ms[session_index] = 0;
+ 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]){ + dsc->wstate[session_index] = buf_tosend; + global_write_dirty = 1; + dsc->wstate[session_index] = buf_set; + dsc->age_ms[session_index] = 0;
- /* copy value if changed (and subscribed) */
- memcpy(dest_p, visible_value_p, sz);
- // else ... : PLC can't wait, variable will be updated next turn
+ /* copy value if changed (and subscribed) */ + memcpy(dest_p, value_p, sz); -static uint32_t send_session_index;
-static int send_iterator(uint32_t index, hmi_tree_item_t *dsc)
+static int send_iterator(uint32_t index, hmi_tree_item_t *dsc, uint32_t session_index) - if(dsc->wstate[send_session_index] == buf_tosend)
+ if(dsc->wstate[session_index] == buf_tosend) uint32_t sz = __get_type_enum_size(dsc->type);
if(sbufidx + sizeof(uint32_t) + sz <= sizeof(sbuf))
@@ -171,7 +178,7 @@
/* TODO : force into little endian */
memcpy(dst_p, &index, sizeof(uint32_t));
memcpy(dst_p + sizeof(uint32_t), src_p, sz);
- dsc->wstate[send_session_index] = buf_free;
+ dsc->wstate[session_index] = buf_free; sbufidx += sizeof(uint32_t) /* index */ + sz;
@@ -184,15 +191,15 @@
-static int read_iterator(uint32_t index, hmi_tree_item_t *dsc)
+static int read_iterator(hmi_tree_item_t *dsc) if(dsc->rstate == buf_set)
void *src_p = &rbuf[dsc->buf_index];
- void *real_value_p = NULL;
- void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
- memcpy(real_value_p, src_p, __get_type_enum_size(dsc->type));
+ UnpackVar(dsc, &value_p, NULL, &sz); + memcpy(value_p, src_p, sz); @@ -200,24 +207,64 @@
void update_refresh_period(hmi_tree_item_t *dsc, uint32_t session_index, uint16_t refresh_period_ms)
- if(refresh_period_ms) {
- if(!dsc->refresh_period_ms[session_index])
+ uint32_t other_session_index = 0; + int previously_subscribed = 0; + int session_only_subscriber = 0; + int session_already_subscriber = 0; + int needs_subscription_for_session = (refresh_period_ms != 0); + while(other_session_index < session_index) { + previously_subscribed |= (dsc->refresh_period_ms[other_session_index++] != 0); + session_already_subscriber = (dsc->refresh_period_ms[other_session_index++] != 0); + while(other_session_index < MAX_CONNECTIONS) { + previously_subscribed |= (dsc->refresh_period_ms[other_session_index++] != 0); + session_only_subscriber = session_already_subscriber && !previously_subscribed; + previously_subscribed |= session_already_subscriber; + if(needs_subscription_for_session) { + if(!session_already_subscriber) dsc->wstate[session_index] = buf_new;
+ /* item is appended to list only when no session was previously subscribed */ + if(!previously_subscribed){ + /* append subsciption to list */ + if(subscriptions_tail != NULL){ + /* if list wasn't empty, link with previous tail*/ + subscriptions_tail->subscriptions_next = dsc; + dsc->subscriptions_prev = subscriptions_tail; + subscriptions_tail = dsc; + dsc->subscriptions_next = NULL; dsc->wstate[session_index] = buf_free;
+ /* item is removed from list only when session was the only one remaining */ + if (session_only_subscriber) { + if(dsc->subscriptions_next == NULL){ /* remove tail */ + /* re-link tail to previous */ + subscriptions_tail = dsc->subscriptions_prev; + if(subscriptions_tail != NULL){ + subscriptions_tail->subscriptions_next = NULL; + } else if(dsc->subscriptions_prev == NULL){ /* remove head */ + dsc->subscriptions_next->subscriptions_prev = NULL; + } else { /* remove entry in between other entries */ + /* re-link previous and next node */ + dsc->subscriptions_next->subscriptions_prev = dsc->subscriptions_prev; + dsc->subscriptions_prev->subscriptions_next = dsc->subscriptions_next; + dsc->subscriptions_next = NULL; + dsc->subscriptions_prev = NULL; dsc->refresh_period_ms[session_index] = refresh_period_ms;
-static uint32_t reset_session_index;
-static int reset_iterator(uint32_t index, hmi_tree_item_t *dsc)
- update_refresh_period(dsc, reset_session_index, 0);
static void *svghmi_handle;
void SVGHMI_SuspendFromPythonThread(void)
@@ -242,7 +289,7 @@
svghmi_handle = create_RT_to_nRT_signal("SVGHMI_pipe");
@@ -258,7 +305,18 @@
if(AtomicCompareExchange(&hmitree_rlock, 0, 1) == 0) {
- traverse_hmi_tree(read_iterator);
+ hmi_tree_item_t *dsc = incoming_tail; + /* iterate through read list (changes from HMI) */ + hmi_tree_item_t *_dsc = dsc->incoming_prev; + dsc->incoming_prev = NULL; AtomicCompareExchange(&hmitree_rlock, 1, 0);
@@ -266,10 +324,16 @@
if(AtomicCompareExchange(&hmitree_wlock, 0, 1) == 0) {
- traverse_hmi_tree(write_iterator);
+ hmi_tree_item_t *dsc = subscriptions_tail; + dsc = dsc->subscriptions_prev; AtomicCompareExchange(&hmitree_wlock, 1, 0);
SVGHMI_WakeupFromRTThread();
@@ -283,16 +347,25 @@
int svghmi_send_collect(uint32_t session_index, uint32_t *size, char **ptr){
if(svghmi_continue_collect) {
- send_session_index = session_index;
while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
- if((res = traverse_hmi_tree(send_iterator)) == 0)
+ hmi_tree_item_t *dsc = subscriptions_tail; + uint32_t index = dsc - hmi_tree_items; + res = send_iterator(index, dsc, session_index); + dsc = dsc->subscriptions_prev; if(sbufidx > HMI_HASH_SIZE){
memcpy(&sbuf[0], &hmi_hash[0], HMI_HASH_SIZE);
@@ -304,7 +377,6 @@
AtomicCompareExchange(&hmitree_wlock, 1, 0);
- // printf("collected BAD result %%d\n", res);
AtomicCompareExchange(&hmitree_wlock, 1, 0);
@@ -322,8 +394,16 @@
int svghmi_reset(uint32_t session_index){
- reset_session_index = session_index;
- traverse_hmi_tree(reset_iterator);
+ hmi_tree_item_t *dsc = subscriptions_tail; + while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){ + hmi_tree_item_t *_dsc = dsc->subscriptions_prev; + update_refresh_period(dsc, session_index, 0); + AtomicCompareExchange(&hmitree_wlock, 1, 0); @@ -355,6 +435,7 @@
AtomicCompareExchange(&hmitree_wlock, 1, 0);
@@ -372,20 +453,20 @@
uint32_t index = *(uint32_t*)(cursor);
uint8_t const *valptr = cursor + sizeof(uint32_t);
if(index == heartbeat_index)
if(index < HMI_ITEM_COUNT)
- hmi_tree_item_t *dsc = &hmi_tree_item[index];
- void *real_value_p = NULL;
- void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
+ hmi_tree_item_t *dsc = &hmi_tree_items[index]; void *dst_p = &rbuf[dsc->buf_index];
- uint32_t sz = __get_type_enum_size(dsc->type);
sz = ((STRING*)valptr)->len + 1;
+ UnpackVar(dsc, NULL, NULL, &sz); @@ -399,7 +480,14 @@
memcpy(dst_p, valptr, sz);
+ /* check that rstate is not already buf_set */ + if(dsc->rstate != buf_set){ + /* append entry to read list (changes from HMI) */ + dsc->incoming_prev = incoming_tail; progress = sz + sizeof(uint32_t) /* index */;
@@ -420,14 +508,20 @@
- reset_session_index = session_index;
while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
- traverse_hmi_tree(reset_iterator);
+ hmi_tree_item_t *dsc = subscriptions_tail; + hmi_tree_item_t *_dsc = dsc->subscriptions_prev; + update_refresh_period(dsc, session_index, 0); @@ -444,7 +538,7 @@
- hmi_tree_item_t *dsc = &hmi_tree_item[index];
+ hmi_tree_item_t *dsc = &hmi_tree_items[index]; update_refresh_period(dsc, session_index, refresh_period_ms);
--- a/svghmi/svghmi.js Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/svghmi.js Sun Jan 16 17:00:58 2022 +0100
@@ -30,12 +30,12 @@
// Open WebSocket to relative "/ws" address
+var has_watchdog = window.location.hash == "#watchdog"; window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
- + '?mode=' + (window.location.hash == "#watchdog"
+ + '?mode=' + (has_watchdog ? "watchdog" : "multiclient"); var ws = new WebSocket(ws_url);
ws.binaryType = 'arraybuffer';
@@ -195,15 +195,28 @@
-// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
-// Since dispatch directly calls change_hmi_value,
-// PLC will periodically send variable at given frequency
-subscribers(heartbeat_index).add({
- /* type: "Watchdog", */
+ // artificially subscribe the watchdog widget to "/heartbeat" hmi variable + // Since dispatch directly calls change_hmi_value, + // PLC will periodically send variable at given frequency + subscribers(heartbeat_index).add({ + /* type: "Watchdog", */ + indexes: [heartbeat_index], + new_hmi_value: function(index, value, oldval) { + apply_hmi_value(heartbeat_index, value+1); +// subscribe to per instance current page hmi variable +// PLC must prefix page name with "!" for page switch to happen +subscribers(current_page_var_index).add({ - indexes: [heartbeat_index],
+ indexes: [current_page_var_index], new_hmi_value: function(index, value, oldval) {
- apply_hmi_value(heartbeat_index, value+1);
+ if(value.startsWith("!")) + switch_page(value.slice(1)); @@ -401,7 +414,9 @@
if(page_name == undefined)
page_name = current_subscribed_page;
+ else if(page_index == undefined){ + [page_name, page_index] = page_name.split('@') let old_desc = page_desc[current_subscribed_page];
let new_desc = page_desc[page_name];
@@ -411,8 +426,19 @@
- if(page_index == undefined){
+ if(page_index == undefined) page_index = new_desc.page_index;
+ else if(typeof(page_index) == "string") { + let hmitree_node = hmitree_nodes[page_index]; + if(hmitree_node !== undefined){ + let [int_index, hmiclass] = hmitree_node; + if(hmiclass == new_desc.page_class) + page_index = int_index; + page_index = new_desc.page_index; + page_index = new_desc.page_index; @@ -443,6 +469,10 @@
if(jump_history.length > 42)
+ apply_hmi_value(current_page_var_index, page_index == undefined + : page_name + "@" + hmitree_paths[page_index]); --- a/svghmi/svghmi.py Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/svghmi.py Sun Jan 16 17:00:58 2022 +0100
@@ -130,6 +130,10 @@
# ignores variables starting with _TMP_
if path[-1].startswith("_TMP_"):
+ # ignores external variables if derived == "HMI_NODE":
@@ -138,7 +142,7 @@
kwargs['hmiclass'] = path[-1]
- new_node = HMITreeNode(path, name, derived, v["type"], v["vartype"], v["C_path"], **kwargs)
+ new_node = HMITreeNode(path, name, derived, v["type"], vartype, v["C_path"], **kwargs) placement_result = hmi_tree_root.place_node(new_node)
if placement_result is not None:
cause, problematic_node = placement_result
@@ -148,10 +152,10 @@
- if v["vartype"] == "FB":
- if v["C_path"] == problematic_node:
+ if _v["vartype"] == "FB": + if _v["C_path"] == problematic_node: failing_parent = last_FB["type"]
@@ -187,14 +191,14 @@
if hasattr(node, "iectype"):
sz = DebugTypesSize.get(node.iectype, 0)
- "{&(" + node.cpath + "), " + node.iectype + {
+ "HMITREE_ITEM_INITIALIZER(" + node.cpath + ", " + node.iectype + { - str(buf_index) + ", 0, }"]
@@ -572,7 +576,8 @@
# call xslt transform on Inkscape's SVG to generate XHTML
self.ProgressStart("xslt", "XSLT transform")
- result = transform.transform(svgdom) # , profile_run=True)
+ result = transform.transform( + svgdom, instance_name=location_str) # , profile_run=True) except XSLTApplyError as e:
self.FatalError("SVGHMI " + svghmi_options["name"] + ": " + e.message)
@@ -826,8 +831,11 @@
self.GetCTRoot().logger.write_error(
_("Font file does not exist: %s\n") % fontfile)
+ def CTNGlobalInstances(self): + location_str = "_".join(map(str, self.GetCurrentLocation())) + return [("CURRENT_PAGE_"+location_str, "HMI_STRING", "")] ## In case one day we support more than one heartbeat
- # def CTNGlobalInstances(self):
# view_name = self.BaseParams.getName()
# return [(view_name + "_HEARTBEAT", "HMI_INT", "")]
--- a/svghmi/widget_jsontable.ysl2 Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/widget_jsontable.ysl2 Sun Jan 16 17:00:58 2022 +0100
@@ -6,7 +6,7 @@
Send given variables as POST to http URL argument, spread returned JSON in
SVG sub-elements of "data" labeled element.
- Documentation to be written. see svbghmi exemple.
+ Documentation to be written. see svghmi exemple. shortdesc > Http POST variables, spread JSON back
@@ -151,15 +151,15 @@
template "svg:use", mode="json_table_elt_render" {
- // cloned element must be part of a HMI:List
+ // cloned element must be part of a HMI:List or a HMI:List const "targetid", "substring-after(@xlink:href,'#')";
const "from_list", "$hmi_lists[(@id | */@id) = $targetid]";
when "count($from_list) > 0" {
- | id("«@id»").setAttribute("xlink:href",
+ | id("«@id»").href.baseVal = // obtain new target id from HMI:List widget
- | "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»]);
+ | "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»]; warning > Clones (svg:use) in JsonTable Widget must point to a valid HMI:List widget or item. Reference "«@xlink:href»" is not valid and will not be updated.
--- a/targets/plc_debug.c Tue Nov 30 18:43:10 2021 +0100
+++ b/targets/plc_debug.c Sun Jan 16 17:00:58 2022 +0100
@@ -25,22 +25,59 @@
+typedef unsigned int dbgvardsc_index_t; +typedef unsigned short trace_buf_offset_t; #ifndef TARGET_ONLINE_DEBUG_DISABLE
-#define BUFFER_SIZE %(buffer_size)d
+#define TRACE_BUFFER_SIZE 4096 +#define TRACE_LIST_SIZE 1024 /* Atomically accessed variable for buffer state */
-static long buffer_state = BUFFER_FREE;
+static long trace_buffer_state = BUFFER_EMPTY; +typedef struct trace_item_s { + dbgvardsc_index_t dbgvardsc_index; +trace_item_t trace_list[TRACE_LIST_SIZE]; +char trace_buffer[TRACE_BUFFER_SIZE]; +static trace_item_t *trace_list_collect_cursor = trace_list; +static trace_item_t *trace_list_addvar_cursor = trace_list; +static const trace_item_t *trace_list_end = + &trace_list[TRACE_LIST_SIZE-1]; +static char *trace_buffer_cursor = trace_buffer; +static const char *trace_buffer_end = trace_buffer + TRACE_BUFFER_SIZE;
-char debug_buffer[BUFFER_SIZE];
+#define FORCE_BUFFER_SIZE 1024 +#define FORCE_LIST_SIZE 256 +typedef struct force_item_s { + dbgvardsc_index_t dbgvardsc_index; + void *value_pointer_backup;
-static char* buffer_cursor = debug_buffer;
+force_item_t force_list[FORCE_LIST_SIZE]; +char force_buffer[FORCE_BUFFER_SIZE]; +static force_item_t *force_list_apply_cursor = force_list; +static force_item_t *force_list_addvar_cursor = force_list; +static const force_item_t *force_list_end = + &force_list[FORCE_LIST_SIZE-1]; +static char *force_buffer_cursor = force_buffer; +static const char *force_buffer_end = force_buffer + FORCE_BUFFER_SIZE; -static unsigned int retain_offset = 0;
@@ -56,10 +93,16 @@
-static dbgvardsc_t dbgvardsc[] = {
+static const dbgvardsc_t dbgvardsc[] = { +static const dbgvardsc_index_t retain_list[] = { +%(retain_vardsc_index_array)s +static unsigned int retain_list_collect_cursor = 0; +static const unsigned int retain_list_size = sizeof(retain_list)/sizeof(dbgvardsc_index_t); typedef void(*__for_each_variable_do_fp)(dbgvardsc_t*);
void __for_each_variable_do(__for_each_variable_do_fp fp)
@@ -77,23 +120,6 @@
void Remind(unsigned int offset, unsigned int count, void * p);
-void RemindIterator(dbgvardsc_t *dsc)
- void *real_value_p = NULL;
- UnpackVar(dsc, &real_value_p, &flags);
- if(flags & __IEC_RETAIN_FLAG){
- USINT size = __get_type_enum_size(dsc->type);
- /* compute next cursor positon*/
- unsigned int next_retain_offset = retain_offset + size;
- /* if buffer not full */
- Remind(retain_offset, size, real_value_p);
- /* increment cursor according size*/
- retain_offset = next_retain_offset;
extern int CheckRetainBuffer(void);
extern void InitRetain(void);
@@ -101,20 +127,46 @@
/* init local static vars */
#ifndef TARGET_ONLINE_DEBUG_DISABLE
- buffer_cursor = debug_buffer;
- buffer_state = BUFFER_FREE;
+ trace_buffer_cursor = trace_buffer; + trace_list_addvar_cursor = trace_list; + trace_list_collect_cursor = trace_list; + trace_buffer_state = BUFFER_EMPTY; + force_buffer_cursor = force_buffer; + force_list_addvar_cursor = force_list; + force_list_apply_cursor = force_list;
/* Iterate over all variables to fill debug buffer */
- __for_each_variable_do(RemindIterator);
+ static unsigned int retain_offset = 0; + retain_list_collect_cursor = 0; + /* iterate over retain list */ + while(retain_list_collect_cursor < retain_list_size){ + dbgvardsc_t *dsc = &dbgvardsc[ + retain_list[retain_list_collect_cursor]]; + UnpackVar(dsc, &value_p, NULL, &size); + printf("Reminding %%d %%ld \n", retain_list_collect_cursor, size); + /* if buffer not full */ + Remind(retain_offset, size, value_p); + /* increment cursor according size*/ + retain_list_collect_cursor++; char mstr[] = "RETAIN memory invalid - defaults used";
LogMessage(LOG_WARNING, mstr, sizeof(mstr));
extern void InitiateDebugTransfer(void);
@@ -125,7 +177,7 @@
void __cleanup_debug(void)
#ifndef TARGET_ONLINE_DEBUG_DISABLE
- buffer_cursor = debug_buffer;
+ trace_buffer_cursor = trace_buffer; @@ -136,84 +188,31 @@
void Retain(unsigned int offset, unsigned int count, void * p);
-static inline void BufferIterator(dbgvardsc_t *dsc, int do_debug)
- void *real_value_p = NULL;
- void *visible_value_p = NULL;
- visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
- if(flags & ( __IEC_DEBUG_FLAG | __IEC_RETAIN_FLAG)){
- USINT size = __get_type_enum_size(dsc->type);
-#ifndef TARGET_ONLINE_DEBUG_DISABLE
- if(flags & __IEC_DEBUG_FLAG){
- /* copy visible variable to buffer */;
- /* compute next cursor positon.
- No need to check overflow, as BUFFER_SIZE
- is computed large enough */
- if(__Is_a_string(dsc)){
- /* optimization for strings */
- size = ((STRING*)visible_value_p)->len + 1;
- char* next_cursor = buffer_cursor + size;
- /* copy data to the buffer */
- memcpy(buffer_cursor, visible_value_p, size);
- /* increment cursor according size*/
- buffer_cursor = next_cursor;
- /* re-force real value of outputs (M and Q)*/
- if((flags & __IEC_FORCE_FLAG) && (flags & __IEC_OUTPUT_FLAG)){
- memcpy(real_value_p, visible_value_p, size);
- if(flags & __IEC_RETAIN_FLAG){
- /* compute next cursor positon*/
- unsigned int next_retain_offset = retain_offset + size;
- /* if buffer not full */
- Retain(retain_offset, size, real_value_p);
- /* increment cursor according size*/
- retain_offset = next_retain_offset;
-void DebugIterator(dbgvardsc_t *dsc){
- BufferIterator(dsc, 1);
-void RetainIterator(dbgvardsc_t *dsc){
- BufferIterator(dsc, 0);
-unsigned int retain_size = 0;
-/* GetRetainSizeIterator */
-void GetRetainSizeIterator(dbgvardsc_t *dsc)
- void *real_value_p = NULL;
- UnpackVar(dsc, &real_value_p, &flags);
- if(flags & __IEC_RETAIN_FLAG){
- USINT size = __get_type_enum_size(dsc->type);
- /* Calc retain buffer size */
/* Return size of all retain variables */
unsigned int GetRetainSize(void)
- __for_each_variable_do(GetRetainSizeIterator);
+ unsigned int retain_size = 0; + retain_list_collect_cursor = 0; + /* iterate over retain list */ + while(retain_list_collect_cursor < retain_list_size){ + dbgvardsc_t *dsc = &dbgvardsc[ + retain_list[retain_list_collect_cursor]]; + UnpackVar(dsc, &value_p, NULL, &size); + retain_list_collect_cursor++; + printf("Retain size %%d \n", retain_size); @@ -226,9 +225,26 @@
extern void ValidateRetainBuffer(void);
extern void InValidateRetainBuffer(void);
+#define __ReForceOutput_case_p(TYPENAME) \ + case TYPENAME##_P_ENUM : \ + case TYPENAME##_O_ENUM : \ + char *next_cursor = force_buffer_cursor + sizeof(TYPENAME); \ + if(next_cursor <= force_buffer_end ){ \ + /* outputs real value must be systematically forced */ \ + if(vartype == TYPENAME##_O_ENUM) \ + /* overwrite value pointed by backup */ \ + *((TYPENAME *)force_list_apply_cursor->value_pointer_backup) = \ + *((TYPENAME *)force_buffer_cursor); \ + /* inc force_buffer cursor */ \ + force_buffer_cursor = next_cursor; \ void __publish_debug(void)
InValidateRetainBuffer();
#ifndef TARGET_ONLINE_DEBUG_DISABLE
@@ -236,116 +252,248 @@
if(TryEnterDebugSection()){
long latest_state = AtomicCompareExchange(
- if(latest_state == BUFFER_FREE)
+ if(latest_state == BUFFER_EMPTY) + /* Reset force list cursor */ + force_list_apply_cursor = force_list; + /* iterate over force list */ + while(!stop && force_list_apply_cursor < force_list_addvar_cursor){ + dbgvardsc_t *dsc = &dbgvardsc[ + force_list_apply_cursor->dbgvardsc_index]; + __IEC_types_enum vartype = dsc->type; + __ANY(__ReForceOutput_case_p) + force_list_apply_cursor++; \ /* Reset buffer cursor */
- buffer_cursor = debug_buffer;
- /* Iterate over all variables to fill debug buffer */
- __for_each_variable_do(DebugIterator);
+ trace_buffer_cursor = trace_buffer; + /* Reset trace list cursor */ + trace_list_collect_cursor = trace_list; + /* iterate over trace list */ + while(trace_list_collect_cursor < trace_list_addvar_cursor){ + dbgvardsc_t *dsc = &dbgvardsc[ + trace_list_collect_cursor->dbgvardsc_index]; + UnpackVar(dsc, &value_p, NULL, &size); + /* copy visible variable to buffer */; + if(__Is_a_string(dsc)){ + /* optimization for strings */ + /* assume NULL terminated strings */ + size = ((STRING*)value_p)->len + 1; + /* compute next cursor positon.*/ + next_cursor = trace_buffer_cursor + size; + /* check for buffer overflow */ + if(next_cursor < trace_buffer_end) + /* copy data to the buffer */ + memcpy(trace_buffer_cursor, value_p, size); + /* stop looping in case of overflow */ + /* increment cursor according size*/ + trace_buffer_cursor = next_cursor; + trace_list_collect_cursor++; * Trigger asynchronous transmission
* (returns immediately) */
InitiateDebugTransfer(); /* size */
- /* when not debugging, do only retain */
- __for_each_variable_do(RetainIterator);
- /* when not debugging, do only retain */
- __for_each_variable_do(RetainIterator);
+ static unsigned int retain_offset = 0; + /* when not debugging, do only retain */ + retain_list_collect_cursor = 0; + /* iterate over retain list */ + while(retain_list_collect_cursor < retain_list_size){ + dbgvardsc_t *dsc = &dbgvardsc[ + retain_list[retain_list_collect_cursor]]; + UnpackVar(dsc, &value_p, NULL, &size); + printf("Retaining %%d %%ld \n", retain_list_collect_cursor, size); + /* if buffer not full */ + Retain(retain_offset, size, value_p); + /* increment cursor according size*/ + retain_list_collect_cursor++; #ifndef TARGET_ONLINE_DEBUG_DISABLE
-#define __RegisterDebugVariable_case_t(TYPENAME) \
- case TYPENAME##_ENUM :\
- ((__IEC_##TYPENAME##_t *)varp)->flags |= flags;\
- ((__IEC_##TYPENAME##_t *)varp)->value = *((TYPENAME *)force);\
+#define TRACE_LIST_OVERFLOW 1 +#define FORCE_LIST_OVERFLOW 2 +#define FORCE_BUFFER_OVERFLOW 3 +#define __ForceVariable_case_t(TYPENAME) \ + case TYPENAME##_ENUM : \ + /* add to force_list*/ \ + force_list_addvar_cursor->dbgvardsc_index = idx; \ + ((__IEC_##TYPENAME##_t *)varp)->flags |= __IEC_FORCE_FLAG; \ + ((__IEC_##TYPENAME##_t *)varp)->value = *((TYPENAME *)force); \ +#define __ForceVariable_case_p(TYPENAME) \ + case TYPENAME##_P_ENUM : \ + case TYPENAME##_O_ENUM : \ + char *next_cursor = force_buffer_cursor + sizeof(TYPENAME); \ + if(next_cursor <= force_buffer_end ){ \ + /* add to force_list*/ \ + force_list_addvar_cursor->dbgvardsc_index = idx; \ + /* save pointer to backup */ \ + force_list_addvar_cursor->value_pointer_backup = \ + ((__IEC_##TYPENAME##_p *)varp)->value; \ + /* store forced value in force_buffer */ \ + *((TYPENAME *)force_buffer_cursor) = *((TYPENAME *)force); \ + /* replace pointer with pointer to force_buffer */ \ + ((__IEC_##TYPENAME##_p *)varp)->value = \ + (TYPENAME *)force_buffer_cursor; \ + /* mark variable as forced */ \ + ((__IEC_##TYPENAME##_p *)varp)->flags |= __IEC_FORCE_FLAG; \ + /* inc force_buffer cursor */ \ + force_buffer_cursor = next_cursor; \ + /* outputs real value must be systematically forced */ \ + if(vartype == TYPENAME##_O_ENUM) \ + *(((__IEC_##TYPENAME##_p *)varp)->value) = *((TYPENAME *)force);\ + error_code = FORCE_BUFFER_OVERFLOW; \ -#define __RegisterDebugVariable_case_p(TYPENAME)\
- case TYPENAME##_P_ENUM :\
- ((__IEC_##TYPENAME##_p *)varp)->flags |= flags;\
- ((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);\
- case TYPENAME##_O_ENUM :\
- ((__IEC_##TYPENAME##_p *)varp)->flags |= flags;\
- ((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);\
- *(((__IEC_##TYPENAME##_p *)varp)->value) = *((TYPENAME *)force);\
+void ResetDebugVariables(void); +int RegisterDebugVariable(dbgvardsc_index_t idx, void* force) + if(idx < sizeof(dbgvardsc)/sizeof(dbgvardsc_t)){ + /* add to trace_list, inc trace_list_addvar_cursor*/ + if(trace_list_addvar_cursor <= trace_list_end){ + trace_list_addvar_cursor->dbgvardsc_index = idx; + trace_list_addvar_cursor++; + error_code = TRACE_LIST_OVERFLOW; + if(force_list_addvar_cursor <= force_list_end){ + dbgvardsc_t *dsc = &dbgvardsc[idx]; + __IEC_types_enum vartype = dsc->type; + __ANY(__ForceVariable_case_t) + __ANY(__ForceVariable_case_p) + /* inc force_list cursor */ + force_list_addvar_cursor++; + error_code = FORCE_LIST_OVERFLOW; + trace_buffer_state = BUFFER_EMPTY; +#define ResetForcedVariable_case_t(TYPENAME) \ + case TYPENAME##_ENUM : \ + ((__IEC_##TYPENAME##_t *)varp)->flags &= ~__IEC_FORCE_FLAG; \ + /* for local variable we don't restore original value */ \ + /* that can be added if needed, but it was like that since ever */ \ -void RegisterDebugVariable(unsigned int idx, void* force)
+#define ResetForcedVariable_case_p(TYPENAME) \ + case TYPENAME##_P_ENUM : \ + case TYPENAME##_O_ENUM : \ + ((__IEC_##TYPENAME##_p *)varp)->flags &= ~__IEC_FORCE_FLAG; \ + /* restore backup to pointer */ \ + ((__IEC_##TYPENAME##_p *)varp)->value = \ + force_list_apply_cursor->value_pointer_backup; \ +void ResetDebugVariables(void) - if(idx < sizeof(dbgvardsc)/sizeof(dbgvardsc_t)){
- unsigned char flags = force ?
- __IEC_DEBUG_FLAG | __IEC_FORCE_FLAG :
- dbgvardsc_t *dsc = &dbgvardsc[idx];
+ trace_list_addvar_cursor = trace_list; + force_list_apply_cursor = force_list; + /* Restore forced variables */ + while(force_list_apply_cursor < force_list_addvar_cursor){ + dbgvardsc_t *dsc = &dbgvardsc[ + force_list_apply_cursor->dbgvardsc_index]; - __ANY(__RegisterDebugVariable_case_t)
- __ANY(__RegisterDebugVariable_case_p)
+ __ANY(ResetForcedVariable_case_t) + __ANY(ResetForcedVariable_case_p)
-#define __ResetDebugVariablesIterator_case_t(TYPENAME) \
- case TYPENAME##_ENUM :\
- ((__IEC_##TYPENAME##_t *)varp)->flags &= ~(__IEC_DEBUG_FLAG|__IEC_FORCE_FLAG);\
-#define __ResetDebugVariablesIterator_case_p(TYPENAME)\
- case TYPENAME##_P_ENUM :\
- case TYPENAME##_O_ENUM :\
- ((__IEC_##TYPENAME##_p *)varp)->flags &= ~(__IEC_DEBUG_FLAG|__IEC_FORCE_FLAG);\
+ /* inc force_list cursor */ + force_list_apply_cursor++; + } /* else TODO: warn user about failure to force */ -void ResetDebugVariablesIterator(dbgvardsc_t *dsc)
- /* force debug flag to 0*/
- __ANY(__ResetDebugVariablesIterator_case_t)
- __ANY(__ResetDebugVariablesIterator_case_p)
-void ResetDebugVariables(void)
- __for_each_variable_do(ResetDebugVariablesIterator);
+ force_list_addvar_cursor = force_list; + /* Reset force buffer */ + force_buffer_cursor = force_buffer; /* atomically mark buffer as free */
int WaitDebugData(unsigned long *tick);
/* Wait until debug data ready and return pointer to it */
int GetDebugData(unsigned long *tick, unsigned long *size, void **buffer){
int wait_error = WaitDebugData(tick);
- *size = buffer_cursor - debug_buffer;
- *buffer = debug_buffer;
+ *size = trace_buffer_cursor - trace_buffer; + *buffer = trace_buffer;