@ali-pay/node-red-contrib-s7
Version:
A Node-RED node to interact with Siemens S7 PLCs
1,438 lines (1,307 loc) • 50.8 kB
HTML
<!--
Copyright: (c) 2016-2020, ST-One Ltda., Guilherme Francescon Cittolin <guilherme@st-one.io>
GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
-->
<script type="text/x-red" data-template-name="s7 endpoint">
<div class="form-row">
<ul style="background: #fff; min-width: 600px; margin-bottom: 20px;" id="node-config-s7-endpoint-tabs"></ul>
</div>
<div id="node-config-s7-endpoint-tabs-content" style="min-height: 170px;">
<div id="s7-endpoint-tab-connection" style="display:none">
<div class="form-row">
<label for="node-config-input-transport"><i class="fa fa-plug"></i> <span data-i18n="s7.endpoint.label.transport"></span></label>
<select type="text" id="node-config-input-transport">
<option value="iso-on-tcp" data-i18n="s7.endpoint.transport.iso-on-tcp"></option>
<option value="mpi-s7" data-i18n="s7.endpoint.transport.mpi-s7"></option>
</select>
</div>
<div id="s7-endpoint-transport-iso-on-tcp" style="display:none; padding-left: 20px;">
<div class="form-row">
<label for="node-config-input-address"><i class="fa fa-globe"></i> <span data-i18n="s7.endpoint.label.address"></span></label>
<input class="input-append-left" type="text" id="node-config-input-address" data-i18n="[placeholder]s7.endpoint.placeholder.address" style="width: 40%;">
<label for="node-config-input-port" style="margin-left: 10px; width: auto; "> <span data-i18n="s7.endpoint.label.port"></span></label>
<input type="text" id="node-config-input-port" data-i18n="[placeholder]s7.endpoint.label.port" style="width: 50px; margin-right: 10px;">
<a id="node-config-lookup-plc-pn" class="btn red-ui-button disabled" data-i18n="[title]s7.endpoint.label.search"><i class="fa fa-search"></i></a>
<a id="node-config-flash-plc-pn" class="btn red-ui-button disabled" data-i18n="[title]s7.endpoint.label.flash-led"><i class="fa fa-lightbulb-o"></i></a>
</div>
<div class="form-row">
<label for="node-config-input-connmode"><i class="fa fa-sliders"></i> <span data-i18n="s7.endpoint.label.connmode"></span></label>
<select type="text" id="node-config-input-connmode">
<option value="rack-slot" data-i18n="s7.endpoint.connmode.rack-slot"></option>
<option value="tsap" data-i18n="s7.endpoint.connmode.tsap"></option>
</select>
</div>
<div class="form-row" id="node-config-s7-endpoint-mode-rackslot">
<label for="node-config-input-rack"><i class="fa fa-sitemap"></i> <span data-i18n="s7.endpoint.label.rack"></span></label>
<input class="input-append-left" type="text" id="node-config-input-rack" data-i18n="[placeholder]s7.endpoint.label.rack" style="width: 50px">
<label for="node-config-input-slot" style="margin-left: 10px; width: 35px; "> <span data-i18n="s7.endpoint.label.slot"></span></label>
<input type="text" id="node-config-input-slot" data-i18n="[placeholder]s7.endpoint.label.slot" style="width:50px">
</div>
<div class="form-row" id="node-config-s7-endpoint-mode-tsap">
<label for="node-config-input-localtsaphi"><i class="fa fa-exchange"></i> <span data-i18n="s7.endpoint.label.tsap-local"></span></label>
<input class="input-append-left" type="text" id="node-config-input-localtsaphi" style="width: 35px"><span><b>.</b></span>
<input class="input-append-left" type="text" id="node-config-input-localtsaplo" style="width: 35px">
<label for="node-config-input-remotetsaphi" style="margin-left: 10px"> <span data-i18n="s7.endpoint.label.tsap-remote"></span></label>
<input class="input-append-left" type="text" id="node-config-input-remotetsaphi" style="width: 35px"><span><b>.</b></span>
<input class="input-append-left" type="text" id="node-config-input-remotetsaplo" style="width: 35px">
</div>
</div>
<div id="s7-endpoint-transport-mpi-s7" style="display:none; padding-left: 20px;">
<div class="form-row">
<label for="node-config-input-adapter"><i class="fa fa-exchange"></i> <span data-i18n="s7.endpoint.label.adapter"></span></label>
<input type="text" id="node-config-input-adapter" data-i18n="[placeholder]s7.endpoint.label.adapter">
<input type="text" id="s7-endpoint-adapter-placeholder" data-i18n="[placeholder]s7.endpoint.label.adapterplaceholder" disabled style="display:none">
</div>
<div class="form-row">
<label for="node-config-input-busaddr"><i class="fa fa-sitemap"></i> <span data-i18n="s7.endpoint.label.busaddr"></span></label>
<input type="text" id="node-config-input-busaddr" style="width: 60px;">
</div>
</div>
<div class="form-row">
<label for="node-config-input-cycletime"><i class="fa fa-refresh"></i> <span data-i18n="s7.endpoint.label.cycletime"></span></label>
<input type="text" id="node-config-input-cycletime" data-i18n="[placeholder]s7.endpoint.label.cycletime" style="width: 60px;"> <span>ms</span>
</div>
<div class="form-row">
<label for="node-config-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="s7.endpoint.label.timeout"></span></label>
<input type="text" id="node-config-input-timeout" data-i18n="[placeholder]s7.endpoint.label.timeout" style="width: 60px;"> <span>ms</span>
</div>
<div class="form-row">
<label for="node-config-input-rewritetimes"><span data-i18n="s7.endpoint.label.rewritetimes"></span></label>
<input type="text" id="node-config-input-rewritetimes" style="width: 60px;">
</div>
<div class="form-row">
<label for="node-config-input-rewriteinterval"><span data-i18n="s7.endpoint.label.rewriteinterval"></span></label>
<input type="text" id="node-config-input-rewriteinterval" style="width: 60px;"> <span>ms</span>
</div>
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="s7.label.name"></span></label>
<input type="text" id="node-config-input-name" data-i18n="[placeholder]s7.label.name">
</div>
</div>
<div id="s7-endpoint-tab-variables" style="display:none">
<div class="form-row" style="margin-bottom:0;">
<label><i class="fa fa-list"></i> <span data-i18n="s7.endpoint.label.variables.list"></span></label>
</div>
<div class="form-row node-input-variables-container-row" style="margin-bottom: 0px;">
<div id="node-config-input-variables-container-div" style="box-sizing: border-box; border-radius: 5px; height: 300px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
<ol id="node-config-input-variables-container" style=" list-style-type:none; margin: 0;"></ol>
</div>
</div>
<div class="form-row">
<a href="#" class="editor-button editor-button-small" id="node-config-s7-endpoint-var-export" style="margin: 4px; float: right"><i class="fa fa-download"></i> <span data-i18n="s7.endpoint.label.variables.export"></span></a>
<input type="file" id="node-config-s7-endpoint-var-import" style="display: none"/>
<a href="#" class="editor-button editor-button-small" id="node-config-s7-endpoint-var-import-btn" style="margin: 4px; float: right"><i class="fa fa-upload"></i> <span data-i18n="s7.endpoint.label.variables.import"></span></a>
<a href="#" class="editor-button editor-button-small" id="node-config-input-add-variable" style="margin: 4px;"><i class="fa fa-plus"></i> <span data-i18n="s7.endpoint.label.variables.add"></span></a>
<a href="#" class="editor-button editor-button-small" id="node-config-s7-endpoint-var-clean" style="margin: 4px;"><i class="fa fa-trash-o"></i> <span data-i18n="s7.endpoint.label.variables.clean"></span></a>
</div>
</div>
</div>
</script>
<script type="text/x-red" data-help-name="s7 endpoint">
<p>Configures the connection to a PLC</p>
<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>
<h3>Details</h3>
<p>
The <b>Cycle time</b> configuration specifies the time interval in which
all variables will be read from the PLC. A value of <code>0</code> disables
automatic reading, and then a manual triggering is required from the
<i>s7 control</i> node
</p>
<p>
The <b>Timeout</b> parameter sets the maximum amount of time that the PLC
may take to answer our requests. It may be desirable to raise this time if
the network is busy of it's latency is high
</p>
<p>
It's possible to log the PLC communication by setting the <b>Debug</b> option
to "On" of "Off". By default, the behavior is controlled by the <code>-v</code>
command-line parameter when running Node-RED
</p>
<p>
In the <b>Transport</b> option it is possible to select method used
to reach the PLC. Different connection options are available depending
on the selected option:
</p>
<h4><i>Ethernet (ISO-on-TCP)</i></h4>
<p>
Connects to the specified ethernet address and port number of the PLC.
</p>
<ul>
<li>
<b>Mode</b>: selects between <i>Rack/Slot</i> and <i>TSAP</i>
connection modes. Usually the <i>Rack/Slot</i> mode is used, except when
connecting to Logo devices where the only option is the <i>TSAP</i>
method
</li>
</ul>
<h4><i>MPI/PPI/DP Adapter</i></h4>
<p>
Connects to the PLC using MPI/PPI/DP Adapters available on the system. Requires
the installation of <code>node-red-contrib-mpi-s7</code>.
</p>
<ul>
<li>
<b>Address</b>: specifies the address on the bus of the PLC
that we will communicate to.
</li>
</ul>
<h3>Notes on S7-1200/S7-1500</h3>
<p>
Some extra configuration may be needed on the S7-1200 and S7-1500 series
of PLCS in order to access their data:
<ul>
<li>
"Optimized block access" must be disabled for the DBs we want
to access
</li>
<li>
In the "Protection" section of the CPU Properties, enable the
"Permit access with PUT/GET" checkbox
</li>
</ul>
</p>
<h3>Variable addressing</h3>
<p>
The variables and their addresses configured on the <strong>S7 Endpoint</strong>
follow a slightly different scheme than used on Step 7 or TIA Portal.
Here are some examples that may guide you on addressing your variables:
</p>
<style>
table {
padding: 0; }
table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
table tr:nth-child(2n) {
background-color: #f8f8f8; }
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr td {
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr th :first-child, table tr td :first-child {
margin-top: 0; }
table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
</style>
<table>
<thead>
<tr>
<th>Address</th>
<th>Step7 equivalent</th>
<th>JS Data type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>DB5,X0.1</code></td>
<td><code>DB5.DBX0.1</code></td>
<td>Boolean</td>
<td>Bit 1 of byte 0 of DB 5</td>
</tr>
<tr>
<td><code>DB23,B1</code> or <code>DB23,BYTE1</code></td>
<td><code>DB23.DBB1</code></td>
<td>Number</td>
<td>Byte 1 (0-255) of DB 23</td>
</tr>
<tr>
<td><code>DB100,C2</code> or <code>DB100,CHAR2</code></td>
<td><code>DB100.DBB2</code></td>
<td>String</td>
<td>Byte 2 of DB 100 as a Char</td>
</tr>
<tr>
<td><code>DB42,I3</code> or <code>DB42,INT3</code></td>
<td><code>DB42.DBW3</code></td>
<td>Number</td>
<td>Signed 16-bit number at byte 3 of DB 42</td>
</tr>
<tr>
<td><code>DB57,WORD4</code></td>
<td><code>DB57.DBW4</code></td>
<td>Number</td>
<td>Unsigned 16-bit number at byte 4 of DB 57</td>
</tr>
<tr>
<td><code>DB13,DI5</code> or <code>DB13,DINT5</code></td>
<td><code>DB13.DBD5</code></td>
<td>Number</td>
<td>Signed 32-bit number at byte 5 of DB 13</td>
</tr>
<tr>
<td><code>DB19,DW6</code> or <code>DB19,DWORD6</code></td>
<td><code>DB19.DBD6</code></td>
<td>Number</td>
<td>Unsigned 32-bit number at byte 6 of DB 19</td>
</tr>
<tr>
<td><code>DB21,R7</code> or <code>DB21,REAL7</code></td>
<td><code>DB19.DBD7</code></td>
<td>Number</td>
<td>Floating point 32-bit number at byte 7 of DB 21</td>
</tr>
<tr>
<td><code>DB2,S7.10</code>*</td>
<td>-</td>
<td>String</td>
<td>String of length 10 starting at byte 7 of DB 2</td>
</tr>
<tr>
<td><code>I1.0</code> or <code>E1.0</code></td>
<td><code>I1.0</code> or <code>E1.0</code></td>
<td>Boolean</td>
<td>Bit 0 of byte 1 of input area</td>
</tr>
<tr>
<td><code>Q2.1</code> or <code>A2.1</code></td>
<td><code>Q2.1</code> or <code>A2.1</code></td>
<td>Boolean</td>
<td>Bit 1 of byte 2 of output area</td>
</tr>
<tr>
<td><code>M3.2</code></td>
<td><code>QM3.2</code></td>
<td>Boolean</td>
<td>Bit 2 of byte 3 of memory area</td>
</tr>
<tr>
<td><code>IB4</code> or <code>EB4</code></td>
<td><code>IB4</code> or <code>EB4</code></td>
<td>Number</td>
<td>Byte 4 (0 -255) of input area</td>
</tr>
<tr>
<td><code>QB5</code> or <code>AB5</code></td>
<td><code>QB5</code> or <code>AB5</code></td>
<td>Number</td>
<td>Byte 5 (0 -255) of output area</td>
</tr>
<tr>
<td><code>MB6</code></td>
<td><code>MB6</code></td>
<td>Number</td>
<td>Byte 6 (0 -255) of memory area</td>
</tr>
<tr>
<td><code>IC7</code> or <code>EC7</code></td>
<td><code>IB7</code> or <code>EB7</code></td>
<td>String</td>
<td>Byte 7 of input area as a Char</td>
</tr>
<tr>
<td><code>QC8</code> or <code>AC8</code></td>
<td><code>QB8</code> or <code>AB8</code></td>
<td>String</td>
<td>Byte 8 of output area as a Char</td>
</tr>
<tr>
<td><code>MC9</code></td>
<td><code>MB9</code></td>
<td>String</td>
<td>Byte 9 of memory area as a Char</td>
</tr>
<tr>
<td><code>II10</code> or <code>EI10</code></td>
<td><code>IW10</code> or <code>EW10</code></td>
<td>Number</td>
<td>Signed 16-bit number at byte 10 of input area</td>
</tr>
<tr>
<td><code>QI12</code> or <code>AI12</code></td>
<td><code>QW12</code> or <code>AW12</code></td>
<td>Number</td>
<td>Signed 16-bit number at byte 12 of output area</td>
</tr>
<tr>
<td><code>MI14</code></td>
<td><code>MW14</code></td>
<td>Number</td>
<td>Signed 16-bit number at byte 14 of memory area</td>
</tr>
<tr>
<td><code>IW16</code> or <code>EW16</code></td>
<td><code>IW16</code> or <code>EW16</code></td>
<td>Number</td>
<td>Unsigned 16-bit number at byte 16 of input area</td>
</tr>
<tr>
<td><code>QW18</code> or <code>AW18</code></td>
<td><code>QW18</code> or <code>AW18</code></td>
<td>Number</td>
<td>Unsigned 16-bit number at byte 18 of output area</td>
</tr>
<tr>
<td><code>MW20</code></td>
<td><code>MW20</code></td>
<td>Number</td>
<td>Unsigned 16-bit number at byte 20 of memory area</td>
</tr>
<tr>
<td><code>IDI22</code> or <code>EDI22</code></td>
<td><code>ID22</code> or <code>ED22</code></td>
<td>Number</td>
<td>Signed 32-bit number at byte 22 of input area</td>
</tr>
<tr>
<td><code>QDI24</code> or <code>ADI24</code></td>
<td><code>QD24</code> or <code>AD24</code></td>
<td>Number</td>
<td>Signed 32-bit number at byte 24 of output area</td>
</tr>
<tr>
<td><code>MDI26</code></td>
<td><code>MD26</code></td>
<td>Number</td>
<td>Signed 32-bit number at byte 26 of memory area</td>
</tr>
<tr>
<td><code>ID28</code> or <code>ED28</code></td>
<td><code>ID28</code> or <code>ED28</code></td>
<td>Number</td>
<td>Unsigned 32-bit number at byte 28 of input area</td>
</tr>
<tr>
<td><code>QD30</code> or <code>AD30</code></td>
<td><code>QD30</code> or <code>AD30</code></td>
<td>Number</td>
<td>Unsigned 32-bit number at byte 30 of output area</td>
</tr>
<tr>
<td><code>MD32</code></td>
<td><code>MD32</code></td>
<td>Number</td>
<td>Unsigned 32-bit number at byte 32 of memory area</td>
</tr>
<tr>
<td><code>IR34</code> or <code>ER34</code></td>
<td><code>IR34</code> or <code>ER34</code></td>
<td>Number</td>
<td>Floating point 32-bit number at byte 34 of input area</td>
</tr>
<tr>
<td><code>QR36</code> or <code>AR36</code></td>
<td><code>QR36</code> or <code>AR36</code></td>
<td>Number</td>
<td>Floating point 32-bit number at byte 36 of output area</td>
</tr>
<tr>
<td><code>MR38</code></td>
<td><code>MR38</code></td>
<td>Number</td>
<td>Floating point 32-bit number at byte 38 of memory area</td>
</tr>
<tr>
<td><code>DB1,DT0</code></td>
<td><code>-</code></td>
<td>Date**</td>
<td>A timestamp in the DATE_AND_TIME format</td>
</tr>
<tr>
<td><code>DB1,DTZ10</code></td>
<td><code>-</code></td>
<td>Date**</td>
<td>A timestamp in the DATE_AND_TIME format, in UTC</td>
</tr>
<tr>
<td><code>DB2,DTL2</code></td>
<td><code>-</code></td>
<td>Date**</td>
<td>A timestamp in the DTL format</td>
</tr>
<tr>
<td><code>DB1,DTLZ12</code></td>
<td><code>-</code></td>
<td>Date**</td>
<td>A timestamp in the DTL format, in UTC</td>
</tr>
<tr>
<td><code>DB57,RWORD4</code></td>
<td><code>DB57.DBW4</code></td>
<td>Number</td>
<td>Unsigned 16-bit number at byte 4 of DB 57, interpreted as Little-Endian</td>
</tr>
<tr>
<td><code>DB13,RDI5</code> or <code>DB13,RDINT5</code></td>
<td><code>DB13.DBD5</code></td>
<td>Number</td>
<td>Signed 32-bit number at byte 5 of DB 13, interpreted as Little-Endian</td>
</tr>
<tr>
<td><code>MRW20</code></td>
<td><code>MRW20</code></td>
<td>Number</td>
<td>Unsigned 16-bit number at byte 20 of memory area, interpreted as Little-Endian</td>
</tr>
</tbody>
</table>
<p>*) Note that strings on the PLC uses 2 extra bytes at start for size/length of the string</p>
<p>**) Note that javascript's <code>Date</code> are <i>always</i> represented in UTC. Please use other nodes like <a href="https://flows.nodered.org/node/node-red-contrib-moment" target="_blank">node-red-contrib-moment</a> to properly handle type conversions</p>
</script>
<script type="text/javascript">
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs = saveAs || function (e) { "use strict"; if (typeof e === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { return } var t = e.document, n = function () { return e.URL || e.webkitURL || e }, r = t.createElementNS("http://www.w3.org/1999/xhtml", "a"), o = "download" in r, a = function (e) { var t = new MouseEvent("click"); e.dispatchEvent(t) }, i = /constructor/i.test(e.HTMLElement) || e.safari, f = /CriOS\/[\d]+/.test(navigator.userAgent), u = function (t) { (e.setImmediate || e.setTimeout)(function () { throw t }, 0) }, s = "application/octet-stream", d = 1e3 * 40, c = function (e) { var t = function () { if (typeof e === "string") { n().revokeObjectURL(e) } else { e.remove() } }; setTimeout(t, d) }, l = function (e, t, n) { t = [].concat(t); var r = t.length; while (r--) { var o = e["on" + t[r]]; if (typeof o === "function") { try { o.call(e, n || e) } catch (a) { u(a) } } } }, p = function (e) { if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)) { return new Blob([String.fromCharCode(65279), e], { type: e.type }) } return e }, v = function (t, u, d) { if (!d) { t = p(t) } var v = this, w = t.type, m = w === s, y, h = function () { l(v, "writestart progress write writeend".split(" ")) }, S = function () { if ((f || m && i) && e.FileReader) { var r = new FileReader; r.onloadend = function () { var t = f ? r.result : r.result.replace(/^data:[^;]*;/, "data:attachment/file;"); var n = e.open(t, "_blank"); if (!n) e.location.href = t; t = undefined; v.readyState = v.DONE; h() }; r.readAsDataURL(t); v.readyState = v.INIT; return } if (!y) { y = n().createObjectURL(t) } if (m) { e.location.href = y } else { var o = e.open(y, "_blank"); if (!o) { e.location.href = y } } v.readyState = v.DONE; h(); c(y) }; v.readyState = v.INIT; if (o) { y = n().createObjectURL(t); setTimeout(function () { r.href = y; r.download = u; a(r); h(); c(y); v.readyState = v.DONE }); return } S() }, w = v.prototype, m = function (e, t, n) { return new v(e, t || e.name || "download", n) }; if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { return function (e, t, n) { t = t || e.name || "download"; if (!n) { e = p(e) } return navigator.msSaveOrOpenBlob(e, t) } } w.abort = function () { }; w.readyState = w.INIT = 0; w.WRITING = 1; w.DONE = 2; w.error = w.onwritestart = w.onprogress = w.onwrite = w.onabort = w.onerror = w.onwriteend = null; return m }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content); if (typeof module !== "undefined" && module.exports) { module.exports.saveAs = saveAs } else if (typeof define !== "undefined" && define !== null && define.amd !== null) { define("FileSaver.js", function () { return saveAs }) }
</script>
<script type="text/javascript">
function validateS7Address(address) {
if (!address) return 'ERR_PARSE_EMPTY';
var NODES7_REGEX_ADDR = /^(?:DB(\d+),)?([A-Z]+)(\d+)(?:\.(\d+))?(?:\.(\d+))?$/;
var NODES7_ADDRTYPE = ["E", "I", "A", "Q", "P", "M", "T", "C"];
var NODES7_DATATYPE = ["X", "B", "C", "I", "DI", "W", "D", "DW", "R", "RI", "RDI", "RW", "RD", "RDW", "RR"];
var NODES7_DATATYPE_DB = ["X", "BYTE", "CHAR", "STRING", "INT", "DINT", "WORD", "DWORD", "REAL", "RINT", "RDINT", "RWORD", "RDWORD", "RREAL", "DT", "DTZ", "DTL", "DTLZ", "B", "C", "S", "I", "DI", "W", "D", "DW", "R", "RI", "RDI", "RW", "RD", "RDW", "RR"];
var match = address.match(NODES7_REGEX_ADDR);
if (!match) return 'ERR_PARSE_UNKNOWN_FORMAT';
var match_db = match[1];
var match_area = match[2];
var match_addr = match[3];
var match_bitAddr = parseInt(match[4]);
var match_arrLen = parseInt(match[5]);
var addrType, dataType, addressOffset, dbNumber;
addressOffset = parseInt(match_addr)
if (isNaN(addressOffset)) return 'ERR_PARSE_ADDR_OFFSET';
if (match_db) {
dbNumber = parseInt(match_db);
if (dbNumber < 1 || isNaN(dbNumber)) return 'ERR_PARSE_DB_NUMBER';
}
if (dbNumber) {
if (!NODES7_DATATYPE_DB.includes(match_area)) return 'ERR_PARSE_DB_DATATYPE';
dataType = match_area;
} else {
// the first char indicates the PLC area (I, Q, M, P, ...)
addrType = match_area.charAt(0);
// the data type (if area is P, skip the first char (I,E,Q,A))
dataType = match_area.substr(addrType === "P" ? 2 : 1);
// validate address type
if (!NODES7_ADDRTYPE.includes(addrType)) return 'ERR_PARSE_AREA';
if (dataType === "" && addrType !== "T" && addrType !== "C") {
dataType = "X";
}
// validate data type
if (!NODES7_DATATYPE.includes(dataType)) return 'ERR_PARSE_DATATYPE';
}
if (dataType === "X") {
// handle array lengths and bit address
if (isNaN(match_bitAddr)) return 'ERR_PARSE_BIT_OFFSET';
if (match_bitAddr > 7) return 'ERR_PARSE_BIT_OFFSET';
} else if (dataType === "STRING" || dataType === "S") {
// match_bitAddr is the string length for string types
if (isNaN(match_bitAddr) || match_bitAddr < 1) return 'ERR_PARSE_STRING_LEN';
} else {
// the array length should be at the bitAddr field, this is a syntax error
if (!isNaN(match_arrLen)) return 'ERR_PARSE_INVALID_BIT_OFFSET';
// the bitAddr field is actually the array length
if (!isNaN(match_bitAddr) && match_bitAddr < 1) return 'ERR_PARSE_INVALID_ARR_LEN';
}
return null;
}
function validateAddressList(list) {
for (var i = 0; i < list.length; i++){
var elm = list[i];
if (!elm.name) return false;
if (validateS7Address(elm.addr)) return false;
}
return true;
}
function validateTSAP(num) {
num = num.toString();
if (num.length != 2) return false;
if (!(/^[0-9a-fA-F]+$/.test(num))) return false;
var i = parseInt(num, 16);
if (isNaN(i) || i < 0 || i > 0xff) return false;
return true;
}
RED.nodes.registerType('s7 endpoint', {
category: 'config',
defaults: {
transport: {
value: "iso-on-tcp"
},
address: {
value: "",
validate: function(v) {return this.transport == 'iso-on-tcp' ? !!v : true}
},
port: {
value: "102",
validate: RED.validators.number()
},
rack: {
value: "0",
validate: RED.validators.number()
},
slot: {
value: "1",
validate: RED.validators.number()
},
localtsaphi: {
value: "01",
validate: validateTSAP
},
localtsaplo: {
value: "00",
validate: validateTSAP
},
remotetsaphi: {
value: "01",
validate: validateTSAP
},
remotetsaplo: {
value: "00",
validate: validateTSAP
},
connmode: {
value: "rack-slot"
},
adapter: {
value: "",
type: "mpi-s7 adapter",
validate: function(v) {return this.transport != "mpi-s7" || !!v }
},
busaddr: {
value: 2
},
cycletime: {
value: 1000
},
timeout: {
value: 2000
},
name: {
value: ""
},
rewritetimes: {
value: 0
},
rewriteinterval: {
value: 500
},
vartable: {
value: [{
name: "",
addr: ""
}],
validate: validateAddressList
}
},
label: function () {
if (this.name) {
return this.name
} else if (this.transport == "iso-on-tcp") {
return this.address + ":" + this.port + "@" + (
this.connmode == 'tsap' ?
(this.localtsaphi + "." + this.localtsaplo + "/" + this.remotetsaphi + "." + this.remotetsaplo) :
(this.rack + ":" + this.slot))
} else if (this.transport == "mpi-s7") {
var adapterNode = RED.nodes.node(this.adapter);
if (adapterNode) {
return adapterNode.label() + "/" + this.busaddr;
}
return "MPI/PPI/DP @ ??/" + this.busaddr;
} else {
return "s7 endpoint";
}
},
oneditprepare: function () {
var self = this;
var tt = this._.bind(this);
var labelName = this._("s7.endpoint.label.variables.name");
var labelAddr = this._("s7.endpoint.label.variables.addr");
var labelDel = this._("s7.endpoint.label.variables.del");
var connTypeList = $('#node-config-input-connmode');
var rackSlotRow = $('#node-config-s7-endpoint-mode-rackslot');
var tsapRow = $('#node-config-s7-endpoint-mode-tsap');
var transportList = $('#node-config-input-transport');
var transportIsoOnTCPGroup = $('#s7-endpoint-transport-iso-on-tcp');
var transportMpiS7Group = $('#s7-endpoint-transport-mpi-s7');
var fieldAddressIsoOnTcp = $('#node-config-input-address');
var fieldName = $('#node-config-input-name');
var lookupPlcPNBtn = $('#node-config-lookup-plc-pn');
var flashPlcPNBtn = $('#node-config-flash-plc-pn');
// put a placeholder for mpi-s7 adapter if not installed
if (!RED.nodes.getType('mpi-s7 adapter')){
$('#node-config-input-adapter').hide();
$('#s7-endpoint-adapter-placeholder').show();
}
// Prepare tabs
var tabs = RED.tabs.create({
id: "node-config-s7-endpoint-tabs",
onchange: function (tab) {
$("#node-config-s7-endpoint-tabs-content").children().hide();
$("#" + tab.id).show();
}
});
tabs.addTab({
id: "s7-endpoint-tab-connection",
label: this._("s7.endpoint.label.tabs.connection")
});
tabs.addTab({
id: "s7-endpoint-tab-variables",
label: this._("s7.endpoint.label.tabs.variables")
});
setTimeout(function () {
tabs.resize()
}, 0);
function generateVariable(variable) {
var curTooltip;
var previousValue = variable.addr;
var container = $('<li/>', {
style: "background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"
});
var row1 = $('<div/>').appendTo(container);
var variableAddr = $('<input/>', {
style: "width: 110px; margin-right: 10px;",
class: "node-config-input-variable-addr",
type: "text",
placeholder: labelAddr
}).appendTo(row1);
var variableName = $('<input/>', {
style: "width: 250px",
class: "node-config-input-variable-name",
type: "text",
placeholder: labelName
}).appendTo(row1);
var finalspan = $('<span/>', {
style: "float: right; margin-right: 10px;"
}).appendTo(row1);
var deleteButton = $('<a/>', {
href: "#",
class: "editor-button editor-button-small",
style: "margin-top: 7px; margin-left: 5px;",
title: labelDel
}).appendTo(finalspan);
$('<i/>', {
class: "fa fa-remove"
}).appendTo(deleteButton);
deleteButton.click(function () {
container.css({
"background": "#fee"
});
container.fadeOut(150, function () {
$(this).remove();
});
if (curTooltip) curTooltip.close();
});
variableAddr.change(function () {
//validate address
var curVal = variableAddr.val();
var valError = validateS7Address(curVal);
if (valError) {
variableAddr.addClass('input-error')
var errorText = tt("s7.validation." + valError);
if (curTooltip) {
curTooltip.setContent(errorText);
curTooltip.open();
} else if (RED.popover && RED.popover.tooltip){
curTooltip = RED.popover.tooltip(variableAddr, errorText);
curTooltip.open();
}
} else {
variableAddr.removeClass('input-error');
if(curTooltip) {
curTooltip.close();
curTooltip.setContent('');
//hack to remove the popup, as Node-RED don't offer
// and "unbind" function. May break in the future
variableAddr.off('mouseenter mouseleave disabled');
curTooltip = null;
}
}
//update name if matching old one
if (previousValue && variableName.val() == previousValue) {
variableName.val(curVal);
}
previousValue = curVal;
});
//populate data
variableAddr.val(variable.addr);
variableName.val(variable.name);
variableAddr.change();
$("#node-config-input-variables-container").append(container);
}
function cleanVarTable() {
$("#node-config-input-variables-container").children().remove();
}
function populateVarTable() {
if (self.vartable) {
if (typeof self.vartable == 'string') {
self.vartable = JSON.parse(self.vartable);
}
for (var i = 0; i < self.vartable.length; i++) {
generateVariable(self.vartable[i]);
}
}
}
$("#node-config-input-add-variable").click(function () {
generateVariable({
name: "",
addr: ""
});
});
$("#node-config-input-rewritetimes").spinner({
min: 0
});
$("#node-config-input-rewriteinterval").spinner({
min: 0
});
$("#node-config-input-cycletime").spinner({
min: 0
});
$("#node-config-input-timeout").spinner({
min: 500
});
$("#node-config-input-busaddr").spinner({
min: 0,
max: 127
});
$("#node-config-s7-endpoint-var-clean").click(cleanVarTable);
populateVarTable();
// export
function exportCSV() {
var vars = $("#node-config-input-variables-container").children();
var lines = [];
vars.each(function (i) {
var elm = $(this);
lines.push([
elm.find(".node-config-input-variable-addr").val(), //addr
elm.find(".node-config-input-variable-name").val() //name
].join(';'));
});
saveAs(new Blob([lines.join('\r\n')]), 's7endpoint' + (self.name ? '_' + self.name : '') + '.csv');
}
$('#node-config-s7-endpoint-var-export').click(exportCSV);
// import
function importCSV(e) {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function (e) {
var res = [], i, fields;
var contents = e.target.result || '';
var lines = contents.split(/[\r\n]+/);
if (!lines.length) {
alert('file is empty!');
return;
}
for (i = 0; i < lines.length; i++) {
lines[i] = lines[i].trim();
if (lines[i] == '') continue;
fields = lines[i].split(/[\t;]/);
if (fields.length < 2) {
alert('line must have at least two parameters, address and name');
return;
}
res.push({
addr: fields[0],
name: fields[1]
});
}
if (res.length) {
cleanVarTable();
self.vartable = res;
populateVarTable();
}
};
reader.readAsText(file);
}
$('#node-config-s7-endpoint-var-import').on('change', importCSV);
$('#node-config-s7-endpoint-var-import-btn').click(function () {
$('#node-config-s7-endpoint-var-import').click();
})
//set defaults for old flows
if (self.connmode === undefined) {
console.log('defaults conmode to "rack-slot"');
self.connmode = 'rack-slot';
self.localtsaphi = "01";
self.localtsaplo = "00";
self.remotetsaphi = "01";
self.remotetsaplo = "00";
$("#node-config-input-localtsaphi").val(self.localtsaphi);
$("#node-config-input-localtsaplo").val(self.localtsaplo);
$("#node-config-input-remotetsaphi").val(self.remotetsaphi);
$("#node-config-input-remotetsaplo").val(self.remotetsaplo);
connTypeList.val(self.connmode);
}
if (self.rewritetimes === undefined) {
self.rewritetimes = 0;
$("#node-config-input-rewritetimes").val(self.rewritetimes);
}
if (self.rewriteinterval === undefined) {
self.rewriteinterval = 500;
$("#node-config-input-rewriteinterval").val(self.rewriteinterval);
}
if (self.timeout === undefined) {
self.timeout = 2000;
$("#node-config-input-timeout").val(self.timeout);
}
if (self.verbose === undefined) {
self.verbose = 'default';
$("#node-config-input-verbose").val(self.verbose);
}
if (self.transport === undefined) {
self.transport = 'iso-on-tcp';
transportList.val(self.transport);
}
connTypeList.change(function () {
if (connTypeList.val() == "rack-slot") {
rackSlotRow.show();
tsapRow.hide();
} else if (connTypeList.val() == "tsap") {
rackSlotRow.hide();
tsapRow.show();
} else {
rackSlotRow.hide();
tsapRow.hide();
}
});
connTypeList.change();
transportList.change(function() {
var curVal = transportList.val();
if(curVal == "iso-on-tcp") {
transportIsoOnTCPGroup.show();
transportMpiS7Group.hide();
} else if (curVal == "mpi-s7") {
transportIsoOnTCPGroup.hide();
transportMpiS7Group.show();
} else {
transportIsoOnTCPGroup.hide();
transportMpiS7Group.hide();
}
});
transportList.change();
$.getJSON('__node-red-contrib-s7/discover/available/iso-on-tcp', function(data) {
if (data) {
lookupPlcPNBtn.removeClass('disabled');
}
});
var lookupPlcPNValue = null;
fieldAddressIsoOnTcp.change(function() {
lookupPlcPNValue = null;
flashPlcPNBtn.addClass('disabled');
})
lookupPlcPNBtn.click(function () {
if (lookupPlcPNBtn.hasClass('disabled')) return;
lookupPlcPNBtn.addClass('disabled');
var iconLookup = lookupPlcPNBtn.children('i');
iconLookup.removeClass('fa-search').addClass('fa-spinner fa-spin fa-fw');
$.getJSON('__node-red-contrib-s7/discover/iso-on-tcp', function (data) {
console.log("node-red-contrib-s7 discovered PLCs", data)
var listData = $.map(data, function(val){
return { label: val, value: val['IP Address']}
})
fieldAddressIsoOnTcp.autocomplete({
source: listData,
minLength: 0,
create: function (event, ui) {
$(this).data('ui-autocomplete')._renderItem = function (ul, elm) {
var item = elm.label;
elmHtml = '<a>'
+ '<small style="float: right;">' + item['Vendor Value'] + '</small>'
+ '<b>' + item['Station Name'] + '</b>'
+ '<br/>'
+ '<small style="float: right;">' + item['MAC Address'] + '</small>'
+ item['IP Address']
+ '</a>'
return $('<li style="padding: 2px">').attr("data-value", elm.value).append(elmHtml).appendTo(ul);
};
},
select: function (event, ui) {
var elm = ui.item.label
event.preventDefault();
event.stopPropagation();
fieldAddressIsoOnTcp.val(elm['IP Address']);
fieldAddressIsoOnTcp.change();
if (!fieldName.val()){
fieldName.val(elm['Station Name']);
}
lookupPlcPNValue = elm;
flashPlcPNBtn.removeClass('disabled');
}
});
fieldAddressIsoOnTcp.focus(function() {
fieldAddressIsoOnTcp.autocomplete("search", "")
});
fieldAddressIsoOnTcp.focus();
}).always(function() {
iconLookup.removeClass('fa-spinner fa-spin fa-fw').addClass('fa-search');
lookupPlcPNBtn.removeClass('disabled');
});
});
flashPlcPNBtn.click(function() {
if (flashPlcPNBtn.hasClass('disabled')) return;
if (!lookupPlcPNValue) {
flashPlcPNBtn.addClass('disabled');
return;
}
var macaddr = lookupPlcPNValue['MAC Address'].replace(/:/g, '-');
var tgtUrl = '__node-red-contrib-s7/flashled/iso-on-tcp/' + macaddr;
$.getJSON(tgtUrl);
});
},
oneditsave: function () {
var node = this;
var vars = $("#node-config-input-variables-container").children();
node.vartable = [];
vars.each(function (i) {
var elm = $(this);
var addr = elm.find(".node-config-input-variable-addr").val();
var name = elm.find(".node-config-input-variable-name").val();
var v = {
addr: addr,
name: name || addr
}
node.vartable.push(v);
});
}
});
</script>
<!-- ######################################################################################## -->
<script type="text/x-red" data-template-name="s7 in">
<div class="form-row">
<label for="node-input-endpoint"><i class="fa fa-bolt"></i> <span data-i18n="s7.in.label.endpoint"></span></label>
<input type="text" id="node-input-endpoint" data-i18n="[placeholder]s7.in.label.endpoint">
</div>
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-sliders"></i> <span data-i18n="s7.in.label.mode"></span></label>
<select type="text" id="node-input-mode">
<option value="single" data-i18n="s7.in.mode.single"></option>
<option value="all-split" data-i18n="s7.in.mode.all-split"></option>
<option value="all" data-i18n="s7.in.mode.all"></option>
</select>
</div>
<div class="form-row s7-input-var-row">
<label for="node-input-variable"><i class="fa fa-random"></i> <span data-i18n="s7.in.label.variable"></span></label>
<select type="text" id="node-input-variable">
</select>
<span id="s7-custom-var-addr"></span>
</div>
<div class="form-row">
<label> </label>
<input type="checkbox" id="node-input-diff" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-diff" style="width:70%;"><span data-i18n="s7.in.label.diff"></span></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="s7.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]s7.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="s7 in">
<p>Reads data from a S7 PLC</p>
<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">any</span></dt>
<dd>
The value(s) as read from the PLC. The format and type of the payload
depends on the configured "Mode"
</dd>
<dt>topic<span class="property-type">string</span></dt>
<dd>
The name of the variable, when the message refers to a single variable
(that is, when mode is "Single Variable" or "All variables, one per
message")
</dd>
</dl>
<h3>Details</h3>
<p>
All data is read cyclically from the PLC as configured in the <i>s7 endpoint</i>,
but there are three modes of making it available in a flow:
</p>
<ul>
<li>
<b>Single variable:</b> A single variable can be selected from the configured
variables, and a message is sent every cycle, or only when it changes if
<i>diff</i> is checked. <code>msg.payload</code> contains the variable's value
and <code>msg.topic</code> has the variable's name.
</li>
<li>
<b>All variables, one per message:</b> Like the <i>Single variable</i> mode,
but for all variables configured. If <i>diff</i> is checked, a message is sent
everytime any variable changes. If <i>diff</i> is unchecked, one message is sent
for every variable, in every cycle. Care must be taken about the number of
messages per second in this mode.
</li>
<li>
<b>All variables:</b> In this mode, <code>msg.payload</code> contains an object
with all configured variables and their values. If <i>diff</i> is checked, a
message is sent if at least one of the variables changes its value.
</li>
</ul>
</script>
<script type="text/javascript">
RED.nodes.registerType('s7 in', {
category: 'plc',
defaults: {
endpoint: {
value: "",
type: "s7 endpoint",
required: true
},
mode: {
value: "single"
},
variable: {
value: ""
},
diff: {
value: true
},
name: {
value: ""
}
},
color: "#3FADB5",
inputs: 0,
outputs: 1,
icon: "serial.png",
label: function () {
var self = this;
if (this.name) return this.name;
var endpointNode = RED.nodes.node(this.endpoint);
if (endpointNode) {
return (this.mode == 'single' ? this.variable : null) || endpointNode.label();
}
return this._("s7.in.label.name");
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
var self = this;
var varList = $('#node-input-variable');
var varAddr = $('#s7-custom-var-addr');
var modeList = $('#node-input-mode');
var endpointList = $("#node-input-endpoint");
var vars = [];
function updateVarList(endpointId) {
$('#node-input-variable option').remove();
var endpointNode = RED.nodes.node(endpointId);
if (!endpointNode) return;
vars = endpointNode.vartable || [];
if (typeof vars === 'string') vars = JSON.parse(vars);
varList.append($('<option/>', {
disabled: "disabled",
selected: "selected",
style: "display:none;",
text: vars.length ? self._("s7.in.label.variable-select") : self._("s7.in.label.variable-novar")
}));
$.each(vars, function (i, val) {
varList.append($('<option/>', {
value: val.name || val.addr,
text: val.name || val.addr
}));
if (val.name == self.variable) {
varList.val(self.variable);
}
});
}
varList.change(function () {
$.each(vars, function (i, val) {
if (varList.val() == val.name) {
varAddr[0].innerText = val.addr;
return true;
}
});
});
endpointList.change(function () {
updateVarList(endpointList.val());
});
updateVarList(self.endpoint);
modeList.change(function () {
if (modeList.val() == "single") {
varList.parent().show();
} else {
varList.parent().hide();
}
});
modeList.change();
}
});
</script>
<!-- ######################################################################################## -->
<script type="text/x-red" data-template-name="s7 out">
<div class="form-row">
<label for="node-input-endpoint"><i class="fa fa-bolt"></i> <span data-i18n="s7.out.label.endpoint"></span></label>
<input type="text" id="node-input-endpoint" data-i18n="[placeholder]s7.out.label.endpoint">
</div>
<div class="form-row s7-input-var-row">
<label for="node-input-variable"><i class="fa fa-random"></i> <span data-i18n="s7.out.label.variable"></span></label>
<select type="text" id="node-input-variable">
</select>
<span id="s7-custom-var-addr"></span>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="s7.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]s7.label.name">
</div>
<div class="form-tips"><span data-i18n="[html]s7.out.disclaimer"></span></div>
</script>
<script type="text/x-red" data-help-name="s7 out">
<p>Writes to a memory area in a S7 PLC</p>
<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>
<p class="form-tips">
<b>Caution when writing data to production PLCs!</b><br/>
Fully test everything you do. The authors cannot be liable for any
damage or injury caused by the use of this node
</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">string | number | boolean | array</span></dt>
<dd>
the data to be written. It will be casted to the required data type
whenever possible.
</dd>
<dt>variable<span class="property-type">string | array</span></dt>
<dd>
the variable to write to on the PLC, according to the variable list
on the PLC endpoint. This takes effect <i>only</i> if the "Variable"
field is left empty on the configuration.
</dd>
</dl>
<h3>Details</h3>
<p>
Writes data to the PLC on the variable specified on the node configuration
or on <code>msg.variable</code>.
</p>
<p>
It is possible to write to multiple variables at once, by setting both
<code>msg.variable</code> and <code>msg.payload</code> to arrays containing
the variable names and their values, respectively. Note that they may not be
written in the same write cycle to the PLC, depending on the amount of variables
to be written.
</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('s7 out', {
category: 'plc',
defaults: {
endpoint: {
value: "",
type: "s7 endpoint",
required: true
},
variable: {
value: ""
},
name: {
value: ""
}
},
color: "#3FADB5",
inputs: 1,
outputs: 1,
icon: "serial.png",
align: 'right',
label: function () {
var self = this;
if (this.name) return this.name;
var endpointNode = RED.nodes.node(this.endpoint);
if (endpointNode) {
return this.variable || endpointNode.label();
}
return this._("s7.out.label.name");
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
var self = this;
var varList = $('#node-input-variable');
var varAddr = $('#s7-custom-var-addr')[0];
var endpointList = $("#node-input-endpoint");
var vars = [];
function updateVarList(endpointId) {
$('#node-input-variable option').remove();
varList.append($('<option/>', {
//disabled: "disabled",
selected: "selected",
//style: "display:none;",
text: self._("s7.out.label.variable-select"),
value: ""
}));
var endpointNode = RED.nodes.node(endpointId);
if (!endpointNode) return;
vars = endpointNode.vartable || [];
if (typeof vars === 'string') vars = JSON.parse(vars);
$.each(vars, function (i, val) {
varList.append($('<option/>', {
value: val.name || val.addr,
text: val.name || val.addr
}));
if (val.name == self.variable) {
varList.val(self.variable);
}
});
}
varList.change(function () {
varAddr.innerText = ""
$.each(vars, function (i, val) {
if (varList.val() == val.name) {
varAddr.innerText = val.addr;
return true;
}
});
});
endpointList.change(function () {
updateVarList(endpointList.val());
});
updateVarList(self.endpoint);
}
});
</script>
<!-- ######################################################################################## -->
<script type="text/x-red" data-template-name="s7 control">
<div class="form-row">
<label for="node-input-endpoint"><i class="fa fa-bolt"></i> <span data-i18n="s7.control.label.endpoint"></span></label>
<input type="text" id="node-input-endpoint" data-i18n="[placeholder]s7.control.label.endpoint">
</div>
<div class="form-row">
<label for="node-input-function"><i class="fa fa-sliders"></i> <span data-i18n="s7.control.label.function"></span></label>
<select type="text" id="node-input-function">
<option value=""></option>
<option value="cycletime" data-i18n="s7.control.function.cycletime"></option>
<option value="trigger" data-i18n="s7.control.function.trigger"></option>
</select>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="s7.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]s7.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="s7 control">
<p>Enables advanced control of the PLC and the connection</p>
<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>
<h3>Details</h3>
<p>The behavior of this node is changed according to the selected function. Each function
has its own configuration, expects different parameters in the messages, and sends
different messages out
</p>
<dl class="message-properties">
<dt>Cycle Time</dt>
<dd>
Changes the time interval between each cyclic read
of variables. It expects a message with <code>payload</code> with a
positive number, being the time in milliseconds between each read. A
value of zero disables the cyclic read.
</dd>
<dt>Trigger read</dt>
<dd>
Manually triggers a read cycle. No message parameters are used and the
same message is sent on the output. Useful when longer cycle times are
used, but an instant feedback is needed (for example after changing a
variable). Note that the <i>s7 in</i> nodes are still required to read
the values of the variables.
</dd>
</dl>
</script>
<script type="text/javascript">
RED.nod