SVGHMI: add CURRENT_PAGE_{location} global variable to reflect currently visible page. If PLC wites some valid page reference in that variable, it triggers page switch. Additionally, fixed /HEARTBEAT being subscribed systematically by JS code even when wtchdog is not enabled.
--- a/svghmi/detachable_pages.ysl2 Fri Oct 29 11:49:22 2021 +0200
+++ b/svghmi/detachable_pages.ysl2 Thu Nov 04 12:00:50 2021 +0100
@@ -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»",
}
| widgets: [
foreach "$page_managed_widgets" {
--- a/svghmi/hmi_tree.py Fri Oct 29 11:49:22 2021 +0200
+++ b/svghmi/hmi_tree.py Thu Nov 04 12:00:50 2021 +0100
@@ -160,5 +160,4 @@
SPECIAL_NODES = [("HMI_ROOT", "HMI_NODE"),
("heartbeat", "HMI_INT")]
- # ("current_page", "HMI_STRING")])
--- a/svghmi/hmi_tree.ysl2 Fri Oct 29 11:49:22 2021 +0200
+++ b/svghmi/hmi_tree.ysl2 Thu Nov 04 12:00:50 2021 +0100
@@ -1,5 +1,7 @@
// hmi_tree.ysl2
+// Location identifies uniquely SVGHMI instance
+param "instance_name";
// 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»;
+ |
| var hmitree_types = [
foreach "$indexed_hmitree/*"
- | /* «@index» */ "«substring(local-name(), 5)»"`if "position()!=last()" > ,`
+ | "«substring(local-name(), 5)»"`if "position()!=last()" > ,`
| ];
|
| var hmitree_paths = [
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/svghmi.js Fri Oct 29 11:49:22 2021 +0200
+++ b/svghmi/svghmi.js Thu Nov 04 12:00:50 2021 +0100
@@ -30,12 +30,12 @@
};
// Open WebSocket to relative "/ws" address
+var has_watchdog = window.location.hash == "#watchdog";
var ws_url =
window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
- + '?mode=' + (window.location.hash == "#watchdog"
- ? "watchdog"
- : "multiclient");
+ + '?mode=' + (has_watchdog ? "watchdog" : "multiclient");
+
var ws = new WebSocket(ws_url);
ws.binaryType = 'arraybuffer';
@@ -195,15 +195,26 @@
}
}
-// 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", */
+if(has_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", */
+ frequency: 1,
+ 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
+subscribers(current_page_var_index).add({
frequency: 1,
- indexes: [heartbeat_index],
+ indexes: [current_page_var_index],
new_hmi_value: function(index, value, oldval) {
- apply_hmi_value(heartbeat_index, value+1);
+ switch_page(value);
}
});
@@ -401,7 +412,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 +424,19 @@
return false;
}
- 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;
+ else
+ page_index = new_desc.page_index;
+ } else {
+ page_index = new_desc.page_index;
+ }
}
if(old_desc){
@@ -443,6 +467,11 @@
if(jump_history.length > 42)
jump_history.shift();
+ apply_hmi_value(current_page_var_index,
+ page_index == undefined
+ ? page_name
+ : page_name + "@" + hmitree_paths[page_index]);
+
return true;
};
--- a/svghmi/svghmi.py Fri Oct 29 11:49:22 2021 +0200
+++ b/svghmi/svghmi.py Thu Nov 04 12:00:50 2021 +0100
@@ -130,6 +130,10 @@
# ignores variables starting with _TMP_
if path[-1].startswith("_TMP_"):
continue
+ vartype = v["vartype"]
+ # ignores external variables
+ if vartype == "EXT":
+ continue
derived = v["derived"]
kwargs={}
if derived == "HMI_NODE":
@@ -138,7 +142,7 @@
kwargs['hmiclass'] = path[-1]
else:
name = 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 @@
".".join(new_node.path))
last_FB = None
- for v in varlist:
- if v["vartype"] == "FB":
- last_FB = v
- if v["C_path"] == problematic_node:
+ for _v in varlist:
+ if _v["vartype"] == "FB":
+ last_FB = _v
+ if _v["C_path"] == problematic_node:
break
if last_FB is not None:
failing_parent = last_FB["type"]
@@ -572,7 +576,8 @@
# call xslt transform on Inkscape's SVG to generate XHTML
try:
self.ProgressStart("xslt", "XSLT transform")
- result = transform.transform(svgdom) # , profile_run=True)
+ result = transform.transform(
+ svgdom, instance_name=location_str) # , profile_run=True)
self.ProgressEnd("xslt")
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/tests/svghmi/plc.xml Fri Oct 29 11:49:22 2021 +0200
+++ b/tests/svghmi/plc.xml Thu Nov 04 12:00:50 2021 +0100
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
<fileHeader companyName="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2019-08-06T14:23:42"/>
- <contentHeader name="Unnamed" modificationDateTime="2021-10-03T20:43:39">
+ <contentHeader name="Unnamed" modificationDateTime="2021-11-04T11:35:21">
<coordinateInfo>
<fbd>
<scaling x="5" y="5"/>
@@ -71,6 +71,25 @@
</type>
</variable>
</localVars>
+ <externalVars>
+ <variable name="CURRENT_PAGE_0">
+ <type>
+ <derived name="HMI_STRING"/>
+ </type>
+ </variable>
+ </externalVars>
+ <localVars>
+ <variable name="PAGESWITCH">
+ <type>
+ <BOOL/>
+ </type>
+ </variable>
+ <variable name="R_TRIG0">
+ <type>
+ <derived name="R_TRIG"/>
+ </type>
+ </variable>
+ </localVars>
</interface>
<body>
<FBD>
@@ -287,6 +306,100 @@
</connectionPointOut>
<expression>0</expression>
</inVariable>
+ <inOutVariable localId="12" executionOrderId="0" height="25" width="125" negatedOut="false" negatedIn="false">
+ <position x="410" y="205"/>
+ <connectionPointIn>
+ <relPosition x="0" y="10"/>
+ <connection refLocalId="13" formalParameter="OUT">
+ <position x="410" y="215"/>
+ <position x="385" y="215"/>
+ </connection>
+ </connectionPointIn>
+ <connectionPointOut>
+ <relPosition x="125" y="10"/>
+ </connectionPointOut>
+ <expression>CURRENT_PAGE_0</expression>
+ </inOutVariable>
+ <block localId="13" typeName="SEL" executionOrderId="0" height="80" width="65">
+ <position x="320" y="185"/>
+ <inputVariables>
+ <variable formalParameter="G">
+ <connectionPointIn>
+ <relPosition x="0" y="30"/>
+ <connection refLocalId="17" formalParameter="Q">
+ <position x="320" y="215"/>
+ <position x="280" y="215"/>
+ </connection>
+ </connectionPointIn>
+ </variable>
+ <variable formalParameter="IN0">
+ <connectionPointIn>
+ <relPosition x="0" y="50"/>
+ <connection refLocalId="12">
+ <position x="320" y="235"/>
+ <position x="60" y="235"/>
+ <position x="60" y="155"/>
+ <position x="550" y="155"/>
+ <position x="550" y="215"/>
+ <position x="535" y="215"/>
+ </connection>
+ </connectionPointIn>
+ </variable>
+ <variable formalParameter="IN1">
+ <connectionPointIn>
+ <relPosition x="0" y="70"/>
+ <connection refLocalId="16">
+ <position x="320" y="255"/>
+ <position x="290" y="255"/>
+ </connection>
+ </connectionPointIn>
+ </variable>
+ </inputVariables>
+ <inOutVariables/>
+ <outputVariables>
+ <variable formalParameter="OUT">
+ <connectionPointOut>
+ <relPosition x="65" y="30"/>
+ </connectionPointOut>
+ </variable>
+ </outputVariables>
+ </block>
+ <inVariable localId="15" executionOrderId="0" height="25" width="90" negated="false">
+ <position x="100" y="205"/>
+ <connectionPointOut>
+ <relPosition x="90" y="10"/>
+ </connectionPointOut>
+ <expression>PAGESWITCH</expression>
+ </inVariable>
+ <inVariable localId="16" executionOrderId="0" height="25" width="220" negated="false">
+ <position x="70" y="245"/>
+ <connectionPointOut>
+ <relPosition x="220" y="10"/>
+ </connectionPointOut>
+ <expression>'RelativePageTest@/TRUMP2'</expression>
+ </inVariable>
+ <block localId="17" typeName="R_TRIG" instanceName="R_TRIG0" executionOrderId="0" height="40" width="60">
+ <position x="220" y="185"/>
+ <inputVariables>
+ <variable formalParameter="CLK">
+ <connectionPointIn>
+ <relPosition x="0" y="30"/>
+ <connection refLocalId="15">
+ <position x="220" y="215"/>
+ <position x="190" y="215"/>
+ </connection>
+ </connectionPointIn>
+ </variable>
+ </inputVariables>
+ <inOutVariables/>
+ <outputVariables>
+ <variable formalParameter="Q">
+ <connectionPointOut>
+ <relPosition x="60" y="30"/>
+ </connectionPointOut>
+ </variable>
+ </outputVariables>
+ </block>
</FBD>
</body>
</pou>