# HG changeset patch # User Edouard Tisserant <edouard.tisserant@gmail.com> # Date 1736442345 -3600 # Node ID 3550841665d333b7acc8f7ae26e268fb4fffff91 # Parent 45a66c3fa78af9d07c8dc08e0c4219edf1e38a8b# Parent 8d150133d225fdbd5080530ad635a46bc90bc9ae Merge remote-tracking branch 'hggit/python3' into python3 diff -r 45a66c3fa78a -r 3550841665d3 exemples/svghmi_csv_json_img_table/py_ext_0@py_ext/pyfile.xml --- a/exemples/svghmi_csv_json_img_table/py_ext_0@py_ext/pyfile.xml Sun Jan 05 23:12:53 2025 +0100 +++ b/exemples/svghmi_csv_json_img_table/py_ext_0@py_ext/pyfile.xml Thu Jan 09 18:05:45 2025 +0100 @@ -16,19 +16,6 @@ class FilesJsonResource(Resource): image_cache = {} - def render_GET(self, request): - request.setHeader('content-type', 'image/png') - img_name = request.args[b'name'][0].decode('utf-8') - p = getcwd() + '/' + img_name - if p not in self.image_cache: - with open(p, 'rb') as image_file: - img_bytes = image_file.read() - self.image_cache[p] = img_bytes - else: - img_bytes = self.image_cache[p] - return img_bytes - - def render_POST(self, request): newstr = request.content.getvalue() newdata = json.loads(newstr) @@ -66,7 +53,7 @@ 'path': dirname(path), 'type': 'folder', 'status': 'active', - 'thumbnail': '/files?name=folder.png' + 'thumbnail': 'folder.png' } ] else: @@ -78,7 +65,7 @@ 'path': join(path, f), 'type': 'folder', 'status': 'active', - 'thumbnail': '/files?name=folder.png' + 'thumbnail': 'folder.png' } for f in ld if not (isfile(join(path, f)) @@ -90,7 +77,7 @@ 'path': join(path, f), 'type': 'file', 'status': 'active', - 'thumbnail': '/files?name=file.png' + 'thumbnail': 'file.png' } for f in ld if isfile(join(path, f)) diff -r 45a66c3fa78a -r 3550841665d3 exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/static/beremiz.png Binary file exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/static/beremiz.png has changed diff -r 45a66c3fa78a -r 3550841665d3 exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/static/file.png Binary file exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/static/file.png has changed diff -r 45a66c3fa78a -r 3550841665d3 exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/static/folder.png Binary file exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/static/folder.png has changed diff -r 45a66c3fa78a -r 3550841665d3 exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/svghmi.svg --- a/exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/svghmi.svg Sun Jan 05 23:12:53 2025 +0100 +++ b/exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/svghmi.svg Thu Jan 09 18:05:45 2025 +0100 @@ -1462,7 +1462,7 @@ inkscape:label="HMI:VarInit:""@.filter" transform="translate(1380)" /><g id="g909-3" - inkscape:label="HMI:VarInit:"/files?name=beremiz.png"@.pic" + inkscape:label="HMI:VarInit:"beremiz.png"@.pic" transform="translate(1380)" /> <g diff -r 45a66c3fa78a -r 3550841665d3 exemples/svghmi_jumps/svghmi_0@svghmi/svghmi.svg --- a/exemples/svghmi_jumps/svghmi_0@svghmi/svghmi.svg Sun Jan 05 23:12:53 2025 +0100 +++ b/exemples/svghmi_jumps/svghmi_0@svghmi/svghmi.svg Thu Jan 09 18:05:45 2025 +0100 @@ -45,12 +45,12 @@ id="namedview4" showgrid="false" inkscape:zoom="1.04375" - inkscape:cx="626.58683" - inkscape:cy="1233.5329" + inkscape:cx="3312.0958" + inkscape:cy="360.71856" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="g845" + inkscape:current-layer="g2763" showguides="true" inkscape:guide-bbox="true" borderlayer="true" @@ -656,6 +656,16 @@ id="g2763" inkscape:label="=1"> <rect + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3.628;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect3080" + width="93.054001" + height="80.952995" + x="3491.4041" + y="94.901985" + ry="20.439945" + rx="20.554585" + inkscape:label="active" /> + <rect rx="21.355932" ry="21.355932" y="93.088097" @@ -663,7 +673,8 @@ height="84.580788" width="96.681831" id="rect114-6" - style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.77952766;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.77953;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + inkscape:label="inactive" /> <text id="text118-3" y="149.95857" @@ -679,14 +690,25 @@ id="g2758" inkscape:label="=2"> <rect - style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.77952766;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3.628;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect3077" + width="93.054001" + height="80.952995" + x="3591.4041" + y="94.901985" + ry="20.439945" + rx="20.554585" + inkscape:label="active" /> + <rect + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.77953;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="rect2531" width="96.681831" height="84.580788" x="3589.5898" y="93.088097" ry="21.355932" - rx="21.355932" /> + rx="21.355932" + inkscape:label="inactive" /> <text xml:space="preserve" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" @@ -702,6 +724,16 @@ id="g2753" inkscape:label="=3"> <rect + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3.628;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect3073" + width="93.054001" + height="80.952995" + x="3691.4041" + y="94.901985" + ry="20.439945" + rx="20.554585" + inkscape:label="active" /> + <rect rx="21.355932" ry="21.355932" y="93.088097" @@ -709,7 +741,8 @@ height="84.580788" width="96.681831" id="rect2533" - style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.77952766;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.77953;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + inkscape:label="inactive" /> <text id="text2561" y="149.95857" @@ -725,14 +758,25 @@ id="g2748" inkscape:label="=4"> <rect - style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.77952766;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3.628;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="rect2535" + width="93.054001" + height="80.952995" + x="3791.4038" + y="94.901985" + ry="20.439945" + rx="20.554585" + inkscape:label="active" /> + <rect + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffa32a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.77953;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect3978" width="96.681831" height="84.580788" x="3789.5898" y="93.088097" ry="21.355932" - rx="21.355932" /> + rx="21.355932" + inkscape:label="inactive" /> <text xml:space="preserve" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" diff -r 45a66c3fa78a -r 3550841665d3 features.py --- a/features.py Sun Jan 05 23:12:53 2025 +0100 +++ b/features.py Thu Jan 09 18:05:45 2025 +0100 @@ -12,7 +12,8 @@ ('Native', 'NativeLib.NativeLibrary', True), ('Python', 'py_ext.PythonLibrary', True), # FIXME ('Etherlab', 'etherlab.EthercatMaster.EtherlabLibrary', False), - ('SVGHMI', 'svghmi.SVGHMILibrary', 'svghmi')] + ('SVGHMI', 'svghmi.SVGHMILibrary', 'svghmi'), + ('MQTT', 'mqtt.MQTTLibrary', False)] catalog = [ ('mqtt', _('MQTT client'), _('Map MQTT topics as located variables'), 'mqtt.MQTTClient'), diff -r 45a66c3fa78a -r 3550841665d3 images/AddFile.png Binary file images/AddFile.png has changed diff -r 45a66c3fa78a -r 3550841665d3 images/DelFile.png Binary file images/DelFile.png has changed diff -r 45a66c3fa78a -r 3550841665d3 images/icons.svg --- a/images/icons.svg Sun Jan 05 23:12:53 2025 +0100 +++ b/images/icons.svg Thu Jan 09 18:05:45 2025 +0100 @@ -42,14 +42,14 @@ bordercolor="#666666" pagecolor="#ffffff" id="base" - showgrid="true" + showgrid="false" inkscape:zoom="11.313708" - inkscape:cx="113.0487" - inkscape:cy="207.40326" + inkscape:cx="1247.3364" + inkscape:cy="197.94571" inkscape:window-x="0" inkscape:window-y="0" inkscape:current-layer="svg2" - showguides="true" + showguides="false" inkscape:guide-bbox="true" inkscape:window-maximized="1" inkscape:measure-start="904.956,703.964" @@ -88341,6 +88341,232 @@ d="m 566.50059,73.36489 c -3.21171,0 -6.18403,1.71679 -7.78988,4.49822 -0.54331,0.94101 -0.88738,1.959 -1.06307,2.99881 h -6.14112 c -2.02768,-0.0286 -2.02768,3.0275 0,2.99882 h 6.14112 c 0.17569,1.03979 0.51976,2.05778 1.06307,2.99881 1.60585,2.78143 4.57817,4.49822 7.78988,4.49822 0.82807,-9e-5 1.49932,-0.67134 1.49941,-1.4994 v -1.49941 h 4.49822 c 2.02743,0.02842 2.02743,-3.027239 0,-2.99882 H 568 v -5.99762 h 4.49822 c 2.02743,0.02842 2.02743,-3.027239 0,-2.99882 H 568 v -1.49941 c -9e-5,-0.82806 -0.67134,-1.49931 -1.49941,-1.4994 z" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill-opacity:1;fill-rule:nonzero;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> </clipPath> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2447-4" + id="linearGradient17971-3" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.04239823,0,0,0.00991741,-845.33196,211.34544)" + x1="302.85999" + y1="366.64999" + x2="302.85999" + y2="609.51001" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient28187-2" + id="radialGradient17973-56" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.01451008,0,0,0.00991741,-831.50006,211.34544)" + cx="605.71002" + cy="486.64999" + r="117.14" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient28187-2" + id="radialGradient17975-2" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.01451008,0,0,0.00991741,-828.51591,211.34544)" + cx="605.71002" + cy="486.64999" + r="117.14" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2435-0" + id="linearGradient17977-7" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.58493109,0,0,0.57567037,-844.0462,187.63732)" + x1="25.132" + y1="0.98521" + x2="25.132" + y2="47.013" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2438-2" + id="linearGradient17979-0" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.47232114,0,0,0.53873584,-809.29599,186.99575)" + x1="-51.785999" + y1="50.785999" + x2="-51.785999" + y2="2.9061999" /> + <radialGradient + inkscape:collect="always" + xlink:href="#radialGradient2432-8" + id="radialGradient17981-61" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.20497637,0,0,-0.22967565,-843.12666,217.0488)" + cx="92.089996" + cy="102.7" + r="139.56" /> + <radialGradient + id="radialGradient2432-8" + gradientUnits="userSpaceOnUse" + cy="102.7" + cx="92.089996" + gradientTransform="matrix(0.17021,0,0,-0.19072,274.84858,216.03233)" + r="139.56"> + <stop + id="stop41-7" + style="stop-color:#b7b8b9" + offset="0" /> + <stop + id="stop47-9" + style="stop-color:#ececec" + offset=".17403" /> + <stop + id="stop49-2" + style="stop-color:#fafafa;stop-opacity:0" + offset=".23908" /> + <stop + id="stop51-0" + style="stop-color:#fff;stop-opacity:0" + offset=".30111" /> + <stop + id="stop53-2" + style="stop-color:#fafafa;stop-opacity:0" + offset=".53130" /> + <stop + id="stop55-3" + style="stop-color:#ebecec;stop-opacity:0" + offset=".84490" /> + <stop + id="stop57-7" + style="stop-color:#e1e2e3;stop-opacity:0" + offset="1" /> + </radialGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2429-7" + id="linearGradient17983-5" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5473824,0,0,0.56012342,-843.14533,188.89357)" + x1="24" + y1="2" + x2="24" + y2="46.016998" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2425-2" + id="linearGradient17985-2" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5754536,0,0,0.66532721,-844.01077,188.39578)" + x1="32.891998" + y1="8.059" + x2="36.358002" + y2="5.4565001" /> + <radialGradient + id="radialGradient2432-6" + gradientUnits="userSpaceOnUse" + cy="102.7" + cx="92.089996" + gradientTransform="matrix(0.17021,0,0,-0.19072,274.84858,216.03233)" + r="139.56"> + <stop + id="stop41-5" + style="stop-color:#b7b8b9" + offset="0" /> + <stop + id="stop47-69" + style="stop-color:#ececec" + offset=".17403" /> + <stop + id="stop49-3" + style="stop-color:#fafafa;stop-opacity:0" + offset=".23908" /> + <stop + id="stop51-7" + style="stop-color:#fff;stop-opacity:0" + offset=".30111" /> + <stop + id="stop53-45" + style="stop-color:#fafafa;stop-opacity:0" + offset=".53130" /> + <stop + id="stop55-2" + style="stop-color:#ebecec;stop-opacity:0" + offset=".84490" /> + <stop + id="stop57-5" + style="stop-color:#e1e2e3;stop-opacity:0" + offset="1" /> + </radialGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2447-4" + id="linearGradient43533" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.04239823,0,0,0.00991741,-917.58729,211.34544)" + x1="302.85999" + y1="366.64999" + x2="302.85999" + y2="609.51001" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient28187-2" + id="radialGradient43535" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-0.01451008,0,0,0.00991741,-903.75539,211.34544)" + cx="605.71002" + cy="486.64999" + r="117.14" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient28187-2" + id="radialGradient43537" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.01451008,0,0,0.00991741,-900.77124,211.34544)" + cx="605.71002" + cy="486.64999" + r="117.14" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2435-0" + id="linearGradient43539" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.58493109,0,0,0.57567037,-916.30153,187.63732)" + x1="25.132" + y1="0.98521" + x2="25.132" + y2="47.013" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2438-2" + id="linearGradient43541" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.47232114,0,0,0.53873584,-881.55132,186.99575)" + x1="-51.785999" + y1="50.785999" + x2="-51.785999" + y2="2.9061999" /> + <radialGradient + inkscape:collect="always" + xlink:href="#radialGradient2432-8" + id="radialGradient43543" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.20497637,0,0,-0.22967565,-915.38199,217.0488)" + cx="92.089996" + cy="102.7" + r="139.56" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2429-7" + id="linearGradient43545" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5473824,0,0,0.56012342,-915.40066,188.89357)" + x1="24" + y1="2" + x2="24" + y2="46.016998" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2425-2" + id="linearGradient43547" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5754536,0,0,0.66532721,-916.2661,188.39578)" + x1="32.891998" + y1="8.059" + x2="36.358002" + y2="5.4565001" /> </defs> <g id="g19063" @@ -88524,7 +88750,7 @@ x="33.295933" id="tspan16193" sodipodi:role="line" - style="font-size:12.761px;line-height:1.25">%% Build Clean editPLC HMIEditor ImportFile ManageFolder ImportSVG NetworkEdit ShowMaster ExportSlave Run ShowIECcode Stop EditSVG OpenPOT EditPO AddFont DelFont %%</tspan></text> + style="font-size:12.761px;line-height:1.25">%% Build Clean editPLC HMIEditor ImportFile ManageFolder ImportSVG NetworkEdit ShowMaster ExportSlave Run ShowIECcode Stop EditSVG OpenPOT EditPO AddFont DelFont AddFile DelFile %%</tspan></text> <rect style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;enable-background:accumulate" id="Unknown" @@ -90515,9 +90741,9 @@ height="1" width="8" id="rect17605" - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" /> <rect - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" id="rect17607" width="8" height="1" @@ -90529,9 +90755,9 @@ height="1" width="4" id="rect17609" - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" /> <rect - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" id="rect17611" width="4" height="1" @@ -90543,9 +90769,9 @@ height="1" width="4" id="rect17613" - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" /> <rect - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" id="rect17615" width="4" height="1" @@ -90557,16 +90783,16 @@ height="1" width="6" id="rect17617" - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" /> <rect y="398.23816" x="-826" height="1" width="4" id="rect17619" - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" /> <rect - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" id="rect17621" width="4" height="1" @@ -90578,9 +90804,9 @@ height="1" width="4" id="rect17623" - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" /> <rect - style="fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + style="display:inline;overflow:visible;visibility:visible;fill:#4fadf7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" id="rect17625" width="4" height="1" @@ -92895,87 +93121,130 @@ transform="matrix(0.53388959,0,0,0.53388959,296.50976,221.2112)" width="744.09448" height="1052.3622" /> + <rect + width="24" + height="24" + x="273.54684" + y="192.51065" + id="ImportFile" + style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;enable-background:accumulate" /> <g - transform="translate(1334.3348,-308.68253)" - id="g16313-4"> + id="g17936" + transform="translate(0.0556,-0.06105)"> <rect - width="24" - height="24" - x="-1060.788" - y="501.19318" - id="ImportFile" - style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;enable-background:accumulate" /> + x="277.24219" + y="214.31532" + width="17" + height="2" + style="opacity:0.15;fill:url(#linearGradient17971)" + id="rect2879" /> + <path + d="m 277.24218,214.31533 v 1.9999 c -0.6205,0.004 -1.5,-0.448 -1.5,-1 0,-0.552 0.6924,-1 1.5,-1 z" + style="opacity:0.15;fill:url(#radialGradient17973)" + id="path2881" + inkscape:connector-curvature="0" /> + <path + d="m 294.24218,214.31533 v 1.9999 c 0.62047,0.004 1.5,-0.44807 1.5,-1.0001 0,-0.552 -0.6924,-0.99982 -1.5,-0.99982 z" + style="opacity:0.15;fill:url(#radialGradient17975)" + id="path2883" + inkscape:connector-curvature="0" /> + <path + d="m 277.24218,192.81529 h 11.5 c 0.683,0.2373 4.541,3.1281 5.5,5 0,5.7292 4e-5,11.271 4e-5,17 h -17 v -22 z" + style="display:inline;fill:url(#linearGradient17977);stroke:url(#linearGradient17979);stroke-width:0.99992;stroke-linejoin:round" + id="path4160" + inkscape:connector-curvature="0" /> + <path + d="m 277.91238,214.31533 c -0.0938,0 -0.1702,-0.086 -0.1702,-0.191 v -20.598 c 0,-0.105 0.0764,-0.1905 0.1702,-0.1905 3.5215,0.0527 7.4238,-0.0788 10.941,0.0131 l 4.839,4.3272 0.05,16.448 c 0,0.105 -0.076,0.191 -0.17,0.191 h -15.66 z" + style="fill:url(#radialGradient17981)" + id="path4191" + inkscape:connector-curvature="0" /> + <path + d="m 293.24218,197.99233 v 15.823 h -15 v -20 h 10.394" + style="opacity:0.6;fill:none;stroke:url(#linearGradient17983)" + id="path2435" + inkscape:connector-curvature="0" /> + <path + d="m 287.81718,193.31533 c 1.1563,0.32877 0.33906,4.6144 0.33906,4.6144 0,0 4.5154,-0.42774 5.6077,1.195 1.489,2.2122 -0.068,-0.6352 -0.173,-0.8217 -0.756,-1.3401 -3.867,-4.5471 -5.046,-4.9412 -0.088,-0.0295 -0.283,-0.0465 -0.728,-0.0465 z" + style="opacity:0.2;fill-rule:evenodd" + id="path3370" + inkscape:connector-curvature="0" /> + <path + d="m 287.74218,193.31533 c 1.5262,0 1,4 1,4 0,0 4.9921,-0.45326 4.9921,2 0,-0.59774 0.0557,-1.4784 -0.0641,-1.6559 -0.839,-1.243 -3.744,-3.8619 -4.798,-4.2976 -0.086,-0.0356 -0.686,-0.0465 -1.13,-0.0465 z" + style="fill:url(#linearGradient17985);fill-rule:evenodd" + id="path4474" + inkscape:connector-curvature="0" /> + </g> + <g + transform="matrix(0.08604141,0,0,0.08604141,274.401,193.46035)" + id="g74019-8"> <g - id="g17936" - transform="translate(-1334.2792,308.62148)"> - <rect - x="277.24219" - y="214.31532" - width="17" - height="2" - style="opacity:0.15;fill:url(#linearGradient17971)" - id="rect2879" /> + transform="matrix(5.0027792,0,0,5.0027792,-215.17835,-168.84627)" + id="g39828-7"> + <circle + cx="100.287" + cy="110.081" + r="34.144001" + transform="matrix(0.3316761,0,0,0.3316761,48.927852,9.2318583)" + id="circle29-8" + style="fill:#84c225;fill-rule:evenodd;stroke:#5d9d35;stroke-width:2.8222" /> <path - d="m 277.24218,214.31533 v 1.9999 c -0.6205,0.004 -1.5,-0.448 -1.5,-1 0,-0.552 0.6924,-1 1.5,-1 z" - style="opacity:0.15;fill:url(#radialGradient17973)" - id="path2881" + d="m 84.515333,38.943636 v 3.981494 h 3.981494 v 4.799639 h -3.981494 v 3.981494 H 79.732961 V 47.724769 H 75.751467 V 42.92513 h 3.981494 v -3.981494 h 4.782372" + id="text1332-4" + style="font-style:normal;font-weight:normal;font-size:12px;font-family:'Bitstream Vera Sans';display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none" inkscape:connector-curvature="0" /> <path - d="m 294.24218,214.31533 v 1.9999 c 0.62047,0.004 1.5,-0.44807 1.5,-1.0001 0,-0.552 -0.6924,-0.99982 -1.5,-0.99982 z" - style="opacity:0.15;fill:url(#radialGradient17975)" - id="path2883" - inkscape:connector-curvature="0" /> - <path - d="m 277.24218,192.81529 h 11.5 c 0.683,0.2373 4.541,3.1281 5.5,5 0,5.7292 4e-5,11.271 4e-5,17 h -17 v -22 z" - style="display:inline;fill:url(#linearGradient17977);stroke:url(#linearGradient17979);stroke-width:0.99992001;stroke-linejoin:round" - id="path4160" - inkscape:connector-curvature="0" /> - <path - d="m 277.91238,214.31533 c -0.0938,0 -0.1702,-0.086 -0.1702,-0.191 v -20.598 c 0,-0.105 0.0764,-0.1905 0.1702,-0.1905 3.5215,0.0527 7.4238,-0.0788 10.941,0.0131 l 4.839,4.3272 0.05,16.448 c 0,0.105 -0.076,0.191 -0.17,0.191 h -15.66 z" - style="fill:url(#radialGradient17981)" - id="path4191" - inkscape:connector-curvature="0" /> - <path - d="m 293.24218,197.99233 v 15.823 h -15 v -20 h 10.394" - style="opacity:0.6;fill:none;stroke:url(#linearGradient17983)" - id="path2435" - inkscape:connector-curvature="0" /> - <path - d="m 287.81718,193.31533 c 1.1563,0.32877 0.33906,4.6144 0.33906,4.6144 0,0 4.5154,-0.42774 5.6077,1.195 1.489,2.2122 -0.068,-0.6352 -0.173,-0.8217 -0.756,-1.3401 -3.867,-4.5471 -5.046,-4.9412 -0.088,-0.0295 -0.283,-0.0465 -0.728,-0.0465 z" - style="opacity:0.2;fill-rule:evenodd" - id="path3370" - inkscape:connector-curvature="0" /> - <path - d="m 287.74218,193.31533 c 1.5262,0 1,4 1,4 0,0 4.9921,-0.45326 4.9921,2 0,-0.59774 0.0557,-1.4784 -0.0641,-1.6559 -0.839,-1.243 -3.744,-3.8619 -4.798,-4.2976 -0.086,-0.0356 -0.686,-0.0465 -1.13,-0.0465 z" - style="fill:url(#linearGradient17985);fill-rule:evenodd" - id="path4474" + d="m 82.190677,35.644078 c 5.025924,0 9.194867,3.673581 9.969229,8.481465 -6.921917,-5.82623 -17.958314,0.09291 -16.467662,9.346307 -2.201037,-1.852678 -3.600446,-4.62745 -3.600446,-7.728889 0,-5.576508 4.522377,-10.098883 10.098879,-10.098883 z" + id="path33-9" + style="fill:url(#linearGradient16528-1);fill-opacity:1;fill-rule:evenodd" inkscape:connector-curvature="0" /> </g> - <g - transform="matrix(0.08604141,0,0,0.08604141,-1059.9338,502.14288)" - id="g74019-8"> - <g - transform="matrix(5.0027792,0,0,5.0027792,-215.17835,-168.84627)" - id="g39828-7"> - <circle - cx="100.287" - cy="110.081" - r="34.144001" - transform="matrix(0.3316761,0,0,0.3316761,48.927852,9.2318583)" - id="circle29-8" - style="fill:#84c225;fill-rule:evenodd;stroke:#5d9d35;stroke-width:2.82220006" /> - <path - d="m 84.515333,38.943636 v 3.981494 h 3.981494 v 4.799639 h -3.981494 v 3.981494 H 79.732961 V 47.724769 H 75.751467 V 42.92513 h 3.981494 v -3.981494 h 4.782372" - id="text1332-4" - style="font-style:normal;font-weight:normal;font-size:12px;font-family:'Bitstream Vera Sans';display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none" - inkscape:connector-curvature="0" /> - <path - d="m 82.190677,35.644078 c 5.025924,0 9.194867,3.673581 9.969229,8.481465 -6.921917,-5.82623 -17.958314,0.09291 -16.467662,9.346307 -2.201037,-1.852678 -3.600446,-4.62745 -3.600446,-7.728889 0,-5.576508 4.522377,-10.098883 10.098879,-10.098883 z" - id="path33-9" - style="fill:url(#linearGradient16528-1);fill-opacity:1;fill-rule:evenodd" - inkscape:connector-curvature="0" /> - </g> - </g> + </g> + <g + id="g17936-3" + transform="matrix(0.83038841,0,0,0.83038841,975.02677,35.736476)" + style="stroke-width:1.20426"> + <rect + x="277.24219" + y="214.31532" + width="17" + height="2" + style="opacity:0.15;fill:url(#linearGradient17971-3);stroke-width:1.20426" + id="rect2879-6" /> + <path + d="m 277.24218,214.31533 v 1.9999 c -0.6205,0.004 -1.5,-0.448 -1.5,-1 0,-0.552 0.6924,-1 1.5,-1 z" + style="opacity:0.15;fill:url(#radialGradient17973-56);stroke-width:1.20426" + id="path2881-1" + inkscape:connector-curvature="0" /> + <path + d="m 294.24218,214.31533 v 1.9999 c 0.62047,0.004 1.5,-0.44807 1.5,-1.0001 0,-0.552 -0.6924,-0.99982 -1.5,-0.99982 z" + style="opacity:0.15;fill:url(#radialGradient17975-2);stroke-width:1.20426" + id="path2883-2" + inkscape:connector-curvature="0" /> + <path + d="m 277.24218,192.81529 h 11.5 c 0.683,0.2373 4.541,3.1281 5.5,5 0,5.7292 4e-5,11.271 4e-5,17 h -17 v -22 z" + style="display:inline;fill:url(#linearGradient17977-7);stroke:url(#linearGradient17979-0);stroke-width:1.20416;stroke-linejoin:round" + id="path4160-9" + inkscape:connector-curvature="0" /> + <path + d="m 277.91238,214.31533 c -0.0938,0 -0.1702,-0.086 -0.1702,-0.191 v -20.598 c 0,-0.105 0.0764,-0.1905 0.1702,-0.1905 3.5215,0.0527 7.4238,-0.0788 10.941,0.0131 l 4.839,4.3272 0.05,16.448 c 0,0.105 -0.076,0.191 -0.17,0.191 h -15.66 z" + style="fill:url(#radialGradient17981-61);stroke-width:1.20426" + id="path4191-3" + inkscape:connector-curvature="0" /> + <path + d="m 293.24218,197.99233 v 15.823 h -15 v -20 h 10.394" + style="opacity:0.6;fill:none;stroke:url(#linearGradient17983-5);stroke-width:1.20426" + id="path2435-1" + inkscape:connector-curvature="0" /> + <path + d="m 287.81718,193.31533 c 1.1563,0.32877 0.33906,4.6144 0.33906,4.6144 0,0 4.5154,-0.42774 5.6077,1.195 1.489,2.2122 -0.068,-0.6352 -0.173,-0.8217 -0.756,-1.3401 -3.867,-4.5471 -5.046,-4.9412 -0.088,-0.0295 -0.283,-0.0465 -0.728,-0.0465 z" + style="opacity:0.2;fill-rule:evenodd;stroke-width:1.20426" + id="path3370-9" + inkscape:connector-curvature="0" /> + <path + d="m 287.74218,193.31533 c 1.5262,0 1,4 1,4 0,0 4.9921,-0.45326 4.9921,2 0,-0.59774 0.0557,-1.4784 -0.0641,-1.6559 -0.839,-1.243 -3.744,-3.8619 -4.798,-4.2976 -0.086,-0.0356 -0.686,-0.0465 -1.13,-0.0465 z" + style="fill:url(#linearGradient17985-2);fill-rule:evenodd;stroke-width:1.20426" + id="path4474-4" + inkscape:connector-curvature="0" /> </g> <g id="g16550-5" @@ -95098,7 +95367,7 @@ style="font-style:normal;font-weight:normal;font-size:10px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1" id="text38073"> <path - d="m 1104,192.36218 v 3 h 3 v 1 h -3 v 3 h -1 v -3 l -2.9971,9.8e-4 -0,-1.00098 h 3 v -3 z" + d="m 1104,192.36218 v 3 h 3 v 1 h -3 v 3 h -1 v -3 l -2.9971,9.8e-4 v -1.00098 h 3 v -3 z" style="fill:#00ff00;fill-opacity:1;stroke-width:1" id="path38075" inkscape:connector-curvature="0" @@ -95114,18 +95383,12 @@ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15.39169598px;line-height:1.25;font-family:MathJax_Script;-inkscape-font-specification:MathJax_Script;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.99999994" id="path38080" inkscape:connector-curvature="0" /> - <g - id="g38084" + <path + inkscape:connector-curvature="0" + id="path38082" style="font-style:normal;font-weight:normal;font-size:10px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fe0000;fill-opacity:1;stroke:none;stroke-width:1" - aria-label="+" - transform="translate(60)"> - <path - inkscape:connector-curvature="0" - id="path38082" - style="fill:#fe0000;fill-opacity:1;stroke-width:1" - d="m 1106,195.36218 v 1 h -6 v -1 z" - sodipodi:nodetypes="ccccc" /> - </g> + d="m 1166,195.36218 v 1 h -6 v -1 z" + sodipodi:nodetypes="ccccc" /> <g id="g52819" transform="rotate(45,111.50001,200.12138)"> @@ -95171,4 +95434,192 @@ d="m 615.63605,136.99738 a 8.9999978,8.9999978 0 0 0 0,12.7279 8.9999978,8.9999978 0 0 0 12.7279,0 8.9999978,8.9999978 0 0 0 0,-12.7279 8.9999978,8.9999978 0 0 0 -12.7279,0 z m 1.59098,1.59098 a 6.7499981,6.7499981 0 0 1 8.68207,-0.72921 l -9.40608,9.40612 a 6.7499981,6.7499981 0 0 1 0.72401,-8.67691 z m 0.8618,10.27309 9.40609,-9.40612 a 6.7499981,6.7499981 0 0 1 -0.72195,8.67897 6.7499981,6.7499981 0 0 1 -8.68414,0.72715 z" id="path16496" inkscape:connector-curvature="0" /> + <rect + inkscape:label="AddFile" + width="24" + height="24" + x="1203.9999" + y="191.36221" + id="AddFile" + style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;enable-background:accumulate" /> + <rect + style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;enable-background:accumulate" + id="DelFile" + y="191.36221" + x="1263.9999" + height="24" + width="24" + inkscape:label="DelFile" /> + <path + inkscape:connector-curvature="0" + id="path43024" + style="font-style:normal;font-weight:normal;font-size:10px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fe0000;fill-opacity:1;stroke:none;stroke-width:1" + d="m 1286,195.36218 v 1 h -6 v -1 z" + sodipodi:nodetypes="ccccc" /> + <g + aria-label="+" + style="font-style:normal;font-weight:normal;font-size:10px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1" + id="g43022" + transform="translate(120)"> + <path + d="m 1104,192.36218 v 3 h 3 v 1 h -3 v 3 h -1 v -3 l -2.9971,9.8e-4 v -1.00098 h 3 v -3 z" + style="fill:#00ff00;fill-opacity:1;stroke-width:1" + id="path43020" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccccccccc" /> + </g> + <g + id="g43531" + transform="matrix(0.83038841,0,0,0.83038841,1035.0268,35.736476)" + style="stroke-width:1.20426"> + <rect + x="277.24219" + y="214.31532" + width="17" + height="2" + style="opacity:0.15;fill:url(#linearGradient43533);stroke-width:1.20426" + id="rect43515" /> + <path + d="m 277.24218,214.31533 v 1.9999 c -0.6205,0.004 -1.5,-0.448 -1.5,-1 0,-0.552 0.6924,-1 1.5,-1 z" + style="opacity:0.15;fill:url(#radialGradient43535);stroke-width:1.20426" + id="path43517" + inkscape:connector-curvature="0" /> + <path + d="m 294.24218,214.31533 v 1.9999 c 0.62047,0.004 1.5,-0.44807 1.5,-1.0001 0,-0.552 -0.6924,-0.99982 -1.5,-0.99982 z" + style="opacity:0.15;fill:url(#radialGradient43537);stroke-width:1.20426" + id="path43519" + inkscape:connector-curvature="0" /> + <path + d="m 277.24218,192.81529 h 11.5 c 0.683,0.2373 4.541,3.1281 5.5,5 0,5.7292 4e-5,11.271 4e-5,17 h -17 v -22 z" + style="display:inline;fill:url(#linearGradient43539);stroke:url(#linearGradient43541);stroke-width:1.20416;stroke-linejoin:round" + id="path43521" + inkscape:connector-curvature="0" /> + <path + d="m 277.91238,214.31533 c -0.0938,0 -0.1702,-0.086 -0.1702,-0.191 v -20.598 c 0,-0.105 0.0764,-0.1905 0.1702,-0.1905 3.5215,0.0527 7.4238,-0.0788 10.941,0.0131 l 4.839,4.3272 0.05,16.448 c 0,0.105 -0.076,0.191 -0.17,0.191 h -15.66 z" + style="fill:url(#radialGradient43543);stroke-width:1.20426" + id="path43523" + inkscape:connector-curvature="0" /> + <path + d="m 293.24218,197.99233 v 15.823 h -15 v -20 h 10.394" + style="opacity:0.6;fill:none;stroke:url(#linearGradient43545);stroke-width:1.20426" + id="path43525" + inkscape:connector-curvature="0" /> + <path + d="m 287.81718,193.31533 c 1.1563,0.32877 0.33906,4.6144 0.33906,4.6144 0,0 4.5154,-0.42774 5.6077,1.195 1.489,2.2122 -0.068,-0.6352 -0.173,-0.8217 -0.756,-1.3401 -3.867,-4.5471 -5.046,-4.9412 -0.088,-0.0295 -0.283,-0.0465 -0.728,-0.0465 z" + style="opacity:0.2;fill-rule:evenodd;stroke-width:1.20426" + id="path43527" + inkscape:connector-curvature="0" /> + <path + d="m 287.74218,193.31533 c 1.5262,0 1,4 1,4 0,0 4.9921,-0.45326 4.9921,2 0,-0.59774 0.0557,-1.4784 -0.0641,-1.6559 -0.839,-1.243 -3.744,-3.8619 -4.798,-4.2976 -0.086,-0.0356 -0.686,-0.0465 -1.13,-0.0465 z" + style="fill:url(#linearGradient43547);fill-rule:evenodd;stroke-width:1.20426" + id="path43529" + inkscape:connector-curvature="0" /> + </g> + <g + id="g60411" + transform="matrix(0.89197136,0,0,0.62499897,131.25613,79.500217)" + style="opacity:1;stroke-width:1.33932" /> + <g + id="g60995"> + <rect + style="color:#000000;overflow:visible;opacity:1;fill:#90e4ff;fill-opacity:1;stroke:none;stroke-width:3.77953;stroke-linecap:round" + id="rect59245" + width="11" + height="10" + x="1207" + y="202" /> + <path + sodipodi:type="star" + style="color:#000000;overflow:visible;opacity:1;fill:#8bbe99;fill-opacity:1;stroke:none;stroke-width:5.06201;stroke-linecap:round" + id="path60193" + inkscape:flatsided="true" + sodipodi:sides="3" + sodipodi:cx="1208.8774" + sodipodi:cy="210.19722" + sodipodi:r1="3.6055512" + sodipodi:r2="1.8027756" + sodipodi:arg1="0.52359878" + sodipodi:arg2="1.5707963" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 1211.9999,212 -6.245,0 3.1225,-5.40833 z" + inkscape:transform-center-y="-0.35546995" + transform="matrix(0.89197136,0,0,0.62499897,131.25613,79.500217)" /> + <path + sodipodi:type="star" + style="color:#000000;overflow:visible;opacity:1;fill:#8bbe99;fill-opacity:1;stroke:none;stroke-width:3.42213;stroke-linecap:round" + id="path60407" + inkscape:flatsided="true" + sodipodi:sides="3" + sodipodi:cx="1213.8774" + sodipodi:cy="210.19722" + sodipodi:r1="3.6055512" + sodipodi:r2="1.8027756" + sodipodi:arg1="0.52359878" + sodipodi:arg2="1.5707963" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 1216.9999,212 -6.245,0 3.1225,-5.40833 z" + inkscape:transform-center-y="-0.52581525" + transform="matrix(1.3194045,0,0,0.92449879,-387.59533,16.006253)" + inkscape:transform-center-x="4.3143207e-05" /> + <circle + style="color:#000000;overflow:visible;opacity:1;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:3.77953;stroke-linecap:round" + id="path60435" + cx="1210.5" + cy="205.5" + r="1.5" /> + </g> + <g + id="g61005" + transform="translate(60)"> + <rect + style="color:#000000;overflow:visible;opacity:1;fill:#90e4ff;fill-opacity:1;stroke:none;stroke-width:3.77953;stroke-linecap:round" + id="rect60997" + width="11" + height="10" + x="1207" + y="202" /> + <path + sodipodi:type="star" + style="color:#000000;overflow:visible;opacity:1;fill:#8bbe99;fill-opacity:1;stroke:none;stroke-width:5.06201;stroke-linecap:round" + id="path60999" + inkscape:flatsided="true" + sodipodi:sides="3" + sodipodi:cx="1208.8774" + sodipodi:cy="210.19722" + sodipodi:r1="3.6055512" + sodipodi:r2="1.8027756" + sodipodi:arg1="0.52359878" + sodipodi:arg2="1.5707963" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 1211.9999,212 -6.245,0 3.1225,-5.40833 z" + inkscape:transform-center-y="-0.35546995" + transform="matrix(0.89197136,0,0,0.62499897,131.25613,79.500217)" /> + <path + sodipodi:type="star" + style="color:#000000;overflow:visible;opacity:1;fill:#8bbe99;fill-opacity:1;stroke:none;stroke-width:3.42213;stroke-linecap:round" + id="path61001" + inkscape:flatsided="true" + sodipodi:sides="3" + sodipodi:cx="1213.8774" + sodipodi:cy="210.19722" + sodipodi:r1="3.6055512" + sodipodi:r2="1.8027756" + sodipodi:arg1="0.52359878" + sodipodi:arg2="1.5707963" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 1216.9999,212 -6.245,0 3.1225,-5.40833 z" + inkscape:transform-center-y="-0.52581525" + transform="matrix(1.3194045,0,0,0.92449879,-387.59533,16.006253)" + inkscape:transform-center-x="4.3143207e-05" /> + <circle + style="color:#000000;overflow:visible;opacity:1;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:3.77953;stroke-linecap:round" + id="circle61003" + cx="1210.5" + cy="205.5" + r="1.5" /> + </g> </svg> diff -r 45a66c3fa78a -r 3550841665d3 mqtt/__init__.py --- a/mqtt/__init__.py Sun Jan 05 23:12:53 2025 +0100 +++ b/mqtt/__init__.py Thu Jan 09 18:05:45 2025 +0100 @@ -3,4 +3,5 @@ from __future__ import absolute_import from .client import MQTTClient +from .library import MQTTLibrary diff -r 45a66c3fa78a -r 3550841665d3 mqtt/client.py --- a/mqtt/client.py Sun Jan 05 23:12:53 2025 +0100 +++ b/mqtt/client.py Thu Jan 09 18:05:45 2025 +0100 @@ -178,7 +178,8 @@ #include "beremiz.h" """ config = self.GetConfig() - c_code += self.modeldata.GenerateC(c_path, locstr, config, self.GetDataTypeInfos) + name = self.BaseParams.getName() + c_code += self.modeldata.GenerateC(name, c_path, locstr, config, self.GetDataTypeInfos) with open(c_path, 'w') as c_file: c_file.write(c_code) diff -r 45a66c3fa78a -r 3550841665d3 mqtt/library.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mqtt/library.py Thu Jan 09 18:05:45 2025 +0100 @@ -0,0 +1,84 @@ +# mqtt/client.py + +from __future__ import absolute_import + +import os + +from POULibrary import POULibrary +import util.paths as paths + +mqtt_python_lib_code = """ +def MQTT_publish(clientname, topic, payload, QoS = 1, Retained = False): + c_function_name = "__mqtt_python_publish_" + clientname + c_function = getattr(PLCBinary, c_function_name) + c_function.restype = ctypes.c_int # error or 0 + c_function.argtypes = [ + ctypes.c_char_p, # topic + ctypes.c_char_p, # data + ctypes.c_uint32, # datalen + ctypes.c_uint8, # QoS + ctypes.c_uint8, # Retained + ] + res = c_function(topic, payload, len(payload), QoS, Retained) + return res + +# C per client CallBack type for __mqtt_python_subscribe_{name} +c_cb_type = ctypes.CFUNCTYPE(ctypes.c_int, # return + ctypes.c_char_p, # topic + ctypes.POINTER(ctypes.c_char), # data + ctypes.c_uint32) # data length + +# CallBacks management +# - each call to MQTT_subscribe registers a callback +MQTT_subscribers_cbs = {} + +# - one callback registered to C side per client +MQTT_client_cbs = {} + +def per_client_cb_factory(client): + def per_client_cb(topic, dataptr, datalen): + payload = ctypes.string_at(dataptr, datalen) + subscriber = MQTT_subscribers_cbs[client].get(topic, None) + if subscriber: + subscriber(topic, payload) + return 0 + return 1 + return per_client_cb + +def MQTT_subscribe(clientname, topic, cb, QoS = 1): + global MQTT_client_cbs, MQTT_subscribers_cbs + c_function_name = "__mqtt_python_subscribe_" + clientname + c_function = getattr(PLCBinary, c_function_name) + c_function.restype = ctypes.c_int # error or 0 + c_function.argtypes = [ + ctypes.c_char_p, # topic + ctypes.c_uint8] # QoS + + MQTT_subscribers_cbs.setdefault(clientname, {})[topic] = cb + + c_cb = MQTT_client_cbs.get(clientname, None) + if c_cb is None: + c_cb = c_cb_type(per_client_cb_factory(clientname)) + MQTT_client_cbs[clientname] = c_cb + register_c_function = getattr(PLCBinary, "__mqtt_python_callback_setter_"+clientname ) + register_c_function.argtypes = [c_cb_type] + register_c_function(c_cb) + + res = c_function(topic, QoS) + return res + +""" + +class MQTTLibrary(POULibrary): + + def GetLibraryPath(self): + return paths.AbsNeighbourFile(__file__, "pous.xml") + + def Generate_C(self, buildpath, varlist, IECCFLAGS): + + runtimefile_path = os.path.join(buildpath, "runtime_00_mqtt.py") + runtimefile = open(runtimefile_path, 'w') + runtimefile.write(mqtt_python_lib_code) + runtimefile.close() + return ((["mqtt"], [], False), "", + ("runtime_00_mqtt.py", open(runtimefile_path, "rb"))) diff -r 45a66c3fa78a -r 3550841665d3 mqtt/mqtt_client_gen.py --- a/mqtt/mqtt_client_gen.py Sun Jan 05 23:12:53 2025 +0100 +++ b/mqtt/mqtt_client_gen.py Thu Jan 09 18:05:45 2025 +0100 @@ -356,7 +356,7 @@ for row in data: writer.writerow([direction] + row) - def GenerateC(self, path, locstr, config, datatype_info_getter): + def GenerateC(self, name, path, locstr, config, datatype_info_getter): c_template_filepath = paths.AbsNeighbourFile(__file__, "mqtt_template.c") c_template_file = open(c_template_filepath , 'rb') c_template = c_template_file.read() @@ -365,6 +365,7 @@ json_types = OD() formatdict = dict( + name = name, locstr = locstr, uri = config["URI"], clientID = config["clientID"], @@ -476,7 +477,8 @@ if item_datatype not in MQTT_JSON_SUPPORTED_types and\ item_datatype not in MQTT_UNSUPPORTED_types: recurseJsonTypes(item_datatype) - def typeCategory(iec_type): + + def typeCategory(field_iec_type): if field_iec_type in arrays: return "ARRAY" elif field_iec_type in structures: diff -r 45a66c3fa78a -r 3550841665d3 mqtt/mqtt_template.c --- a/mqtt/mqtt_template.c Sun Jan 05 23:12:53 2025 +0100 +++ b/mqtt/mqtt_template.c Thu Jan 09 18:05:45 2025 +0100 @@ -30,7 +30,7 @@ // MQTTCLIENT_TRACE_PROTOCOL, MQTTCLIENT_TRACE_MAXIMUM, MQTTCLIENT_TRACE_ERROR #define MQTT_DEBUG_LEVEL MQTTCLIENT_TRACE_ERROR -void trace_callback(enum MQTTCLIENT_TRACE_LEVELS level, char* message) +static void trace_callback(enum MQTTCLIENT_TRACE_LEVELS level, char* message) {{ if(level >= MQTT_DEBUG_LEVEL) {{ @@ -148,7 +148,7 @@ }} #define DECL_JSON_INPUT(C_type, c_loc_name) \ -int json_parse_##c_loc_name(char *json, const int len, void *void_ptr) {{ \ +static int json_parse_##c_loc_name(char *json, const int len, void *void_ptr) {{ \ C_type *struct_ptr = (C_type *)void_ptr; \ return json_scanf(json, len, "{{" TYPE_##C_type(scanf_fmt,) "}}", TYPE_##C_type(scanf_args, struct_ptr)); \ }} @@ -159,7 +159,7 @@ static int json_out_len = 0; #define DECL_JSON_OUTPUT(C_type, c_loc_name) \ -int json_gen_##c_loc_name(C_type *struct_ptr) {{ \ +static int json_gen_##c_loc_name(C_type *struct_ptr) {{ \ struct json_out out = JSON_OUT_BUF(json_out_buf, json_out_size); \ json_out_len = json_printf(&out, "{{" TYPE_##C_type(printf_fmt,) "}}", TYPE_##C_type(printf_args, struct_ptr)); \ if(json_out_len > json_out_size){{ \ @@ -247,7 +247,7 @@ MQTTClient_destroy(&client); }} -void connectionLost(void* context, char* reason) +static void connectionLost(void* context, char* reason) {{ int rc; LogWarning("ConnectionLost, reconnecting\\n"); @@ -260,8 +260,10 @@ }} - -int messageArrived(void *context, char *topicName, int topicLen, MQTTClient_message *message) +typedef int(*callback_fptr_t)(char* topic, char* data, uint32_t datalen); +static callback_fptr_t __mqtt_python_callback_fptr_{name} = NULL; + +static int messageArrived(void *context, char *topicName, int topicLen, MQTTClient_message *message) {{ int low = 0; int size = sizeof(topics) / sizeof(topics[0]); @@ -307,7 +309,15 @@ high = mid - 1; }} // If we reach here, then the element was not present - LogWarning("MQTT unknown topic: %s\n", topicName); + if(__mqtt_python_callback_fptr_{name} && + (*__mqtt_python_callback_fptr_{name})(topicName, + (char*)message->payload, + message->payloadlen) == 0){{ + // Topic was handled in python + goto exit; + }} else {{ + LogWarning("MQTT unknown topic: %s\n", topicName); + }} goto exit; found: @@ -357,19 +367,19 @@ #ifdef USE_MQTT_5 #define _SUBSCRIBE(Topic, QoS) \ - MQTTResponse response = MQTTClient_subscribe5(client, #Topic, QoS, NULL, NULL); \ + MQTTResponse response = MQTTClient_subscribe5(client, Topic, QoS, NULL, NULL); \ /* when using MQTT5 responce code is 1 for some reason even if no error */ \ rc = response.reasonCode == 1 ? MQTTCLIENT_SUCCESS : response.reasonCode; \ MQTTResponse_free(response); #else #define _SUBSCRIBE(Topic, QoS) \ - rc = MQTTClient_subscribe(client, #Topic, QoS); + rc = MQTTClient_subscribe(client, Topic, QoS); #endif #define INIT_SUBSCRIPTION(Topic, QoS) \ {{ \ int rc; \ - _SUBSCRIBE(Topic, QoS) \ + _SUBSCRIBE(#Topic, QoS) \ if (rc != MQTTCLIENT_SUCCESS) \ {{ \ LogError("MQTT client failed to subscribe to '%s', return code %d\n", #Topic, rc); \ @@ -379,18 +389,18 @@ #ifdef USE_MQTT_5 #define _PUBLISH(Topic, QoS, cstring_size, cstring_ptr, Retained) \ - MQTTResponse response = MQTTClient_publish5(client, #Topic, cstring_size, \ + MQTTResponse response = MQTTClient_publish5(client, Topic, cstring_size, \ cstring_ptr, QoS, Retained, NULL, NULL); \ rc = response.reasonCode; \ MQTTResponse_free(response); #else #define _PUBLISH(Topic, QoS, cstring_size, cstring_ptr, Retained) \ - rc = MQTTClient_publish(client, #Topic, cstring_size, \ + rc = MQTTClient_publish(client, Topic, cstring_size, \ cstring_ptr, QoS, Retained, NULL); #endif #define PUBLISH_SIMPLE(Topic, QoS, C_type, c_loc_name, Retained) \ - _PUBLISH(Topic, QoS, sizeof(C_type), &MQTT_##c_loc_name##_buf, Retained) + _PUBLISH(#Topic, QoS, sizeof(C_type), &MQTT_##c_loc_name##_buf, Retained) #define PUBLISH_JSON(Topic, QoS, C_type, c_loc_name, Retained) \ int res = json_gen_##c_loc_name(&MQTT_##c_loc_name##_buf); \ @@ -589,3 +599,39 @@ }} }} +int __mqtt_python_publish_{name}(char* topic, char* data, uint32_t datalen, uint8_t QoS, uint8_t Retained) +{{ + int rc = 0; + + if((rc = pthread_mutex_lock(&MQTT_thread_wakeup_mutex)) == 0){{ + _PUBLISH(topic, QoS, datalen, data, Retained) + pthread_mutex_unlock(&MQTT_thread_wakeup_mutex); + if (rc != MQTTCLIENT_SUCCESS){{ + LogError("MQTT python can't publish \"%s\", return code %d\n", topic, rc); + return rc; + }} + }} else {{ + LogError("MQTT python can't obtain lock, return code %d\n", rc); + return rc; + }} + return 0; +}} + + +int __mqtt_python_subscribe_{name}(char* topic, uint8_t QoS) +{{ + int rc; + _SUBSCRIBE(topic, QoS) + if (rc != MQTTCLIENT_SUCCESS) + {{ + LogError("MQTT client failed to subscribe to '%s', return code %d\n", topic, rc); + return rc; + }} + return 0; +}} + +int __mqtt_python_callback_setter_{name}(callback_fptr_t cb) +{{ + __mqtt_python_callback_fptr_{name} = cb; + return 0; +}} diff -r 45a66c3fa78a -r 3550841665d3 mqtt/pous.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mqtt/pous.xml Thu Jan 09 18:05:45 2025 +0100 @@ -0,0 +1,24 @@ +<?xml version='1.0' encoding='utf-8'?> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201"> + <fileHeader companyName="Beremiz" productName="Beremiz" productVersion="0.0" creationDateTime="2008-12-14T16:53:26"/> + <contentHeader name="Beremiz non-standard POUs library" modificationDateTime="2019-08-06T14:08:26"> + <coordinateInfo> + <fbd> + <scaling x="0" y="0"/> + </fbd> + <ld> + <scaling x="0" y="0"/> + </ld> + <sfc> + <scaling x="0" y="0"/> + </sfc> + </coordinateInfo> + </contentHeader> + <types> + <dataTypes/> + <pous/> + </types> + <instances> + <configurations/> + </instances> +</project> diff -r 45a66c3fa78a -r 3550841665d3 runtime/PLCObject.py --- a/runtime/PLCObject.py Sun Jan 05 23:12:53 2025 +0100 +++ b/runtime/PLCObject.py Thu Jan 09 18:05:45 2025 +0100 @@ -591,46 +591,59 @@ return getPSKID(partial(self.LogMessage, 0)) def _init_blobs(self): - self.blobs = {} + self.blobs = {} # dict of list if os.path.exists(self.tmpdir): shutil.rmtree(self.tmpdir) os.mkdir(self.tmpdir) + def _append_blob(self, blob, newBlobID): + self.blobs.setdefault(newBlobID,[]).append(blob) + + def _pop_blob(self, blobID): + blobs = self.blobs.pop(blobID, None) + + if blobs is None: + return None + + blob = blobs.pop() + if blobs: + # insert same blob list back if not empty + blobs = self.blobs[blobID] = blobs + return blob + @RunInMain def SeedBlob(self, seed): blob = (mkstemp(dir=self.tmpdir) + (hashlib.new('md5'),)) _fd, _path, md5sum = blob md5sum.update(seed) newBlobID = md5sum.digest() - self.blobs[newBlobID] = blob + self._append_blob(blob, newBlobID) return newBlobID @RunInMain def AppendChunkToBlob(self, data, blobID): - blob = self.blobs.pop(blobID, None) - - if blob is None: - return None + blob = self._pop_blob(blobID) fd, _path, md5sum = blob md5sum.update(data) newBlobID = md5sum.digest() os.write(fd, data) - self.blobs[newBlobID] = blob + self._append_blob(blob, newBlobID) return newBlobID @RunInMain def PurgeBlobs(self): - for fd, _path, _md5sum in list(self.blobs.values()): - os.close(fd) + for blobs in list(self.blobs.values()): + for fd, _path, _md5sum in blobs: + os.close(fd) self._init_blobs() def BlobAsFile(self, blobID, newpath): - blob = self.blobs.pop(blobID, None) + blob = self._pop_blob(blobID) if blob is None: raise Exception( - _(f"Missing data to create file: {newpath}").decode()) + _(f"Missing data to create file: {newpath}")) self._BlobAsFile(blob, newpath) diff -r 45a66c3fa78a -r 3550841665d3 svghmi/analyse_widget.xslt --- a/svghmi/analyse_widget.xslt Sun Jan 05 23:12:53 2025 +0100 +++ b/svghmi/analyse_widget.xslt Thu Jan 09 18:05:45 2025 +0100 @@ -507,7 +507,7 @@ <longdesc> <xsl:text>ForEach widget is used to span a small set of widget over a larger set of </xsl:text> - <xsl:text>repeated HMI_NODEs. + <xsl:text>repeated HMI_NODEs. </xsl:text> <xsl:text> </xsl:text> diff -r 45a66c3fa78a -r 3550841665d3 svghmi/gen_index_xhtml.xslt --- a/svghmi/gen_index_xhtml.xslt Sun Jan 05 23:12:53 2025 +0100 +++ b/svghmi/gen_index_xhtml.xslt Thu Jan 09 18:05:45 2025 +0100 @@ -4475,7 +4475,7 @@ <longdesc> <xsl:text>ForEach widget is used to span a small set of widget over a larger set of </xsl:text> - <xsl:text>repeated HMI_NODEs. + <xsl:text>repeated HMI_NODEs. </xsl:text> <xsl:text> </xsl:text> @@ -4951,14 +4951,11 @@ </xsl:text> <xsl:text> }, </xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> init: function() { -</xsl:text> - <xsl:text> this.animate(); -</xsl:text> - <xsl:text> }, -</xsl:text> + </xsl:template> + <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="svg:image[starts-with(@inkscape:label, 'HMI:Image')]"> + <xsl:copy> + <xsl:apply-templates mode="inline_svg" select="@*[not(contains(name(), 'href'))] | node()"/> + </xsl:copy> </xsl:template> <xsl:template match="widget[@type='Input']" mode="widget_desc"> <type> @@ -4997,12 +4994,6 @@ <xsl:text>InputWidget</xsl:text> <xsl:text> extends Widget{ </xsl:text> - <xsl:text> on_op_click(opstr) { -</xsl:text> - <xsl:text> this.change_hmi_value(0, opstr); -</xsl:text> - <xsl:text> } -</xsl:text> <xsl:text> edit_callback(new_val) { </xsl:text> <xsl:text> this.apply_hmi_value(0, new_val); @@ -5143,6 +5134,58 @@ <xsl:value-of select="@id"/> <xsl:text>"), </xsl:text> + <xsl:variable name="current_id" select="@id"/> + <xsl:variable name="active" select="$hmi_element/*[@id = $current_id]/*[regexp:test(@inkscape:label,'active')]"/> + <xsl:text> activable_sub_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text>: { +</xsl:text> + <xsl:for-each select="$active"> + <xsl:text> </xsl:text> + <xsl:value-of select="@inkscape:label"/> + <xsl:text>_elt: id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>")</xsl:text> + <xsl:if test="position()!=last()"> + <xsl:text>,</xsl:text> + </xsl:if> + <xsl:text> +</xsl:text> + </xsl:for-each> + <xsl:text> }, +</xsl:text> + <xsl:text> on_op_mouse_down_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text>: function(){ +</xsl:text> + <xsl:text> svg_root.addEventListener("pointerup", this.bound_on_op_mouse_up_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text>, true); +</xsl:text> + <xsl:text> set_activity_state(this.activable_sub_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text>, true); +</xsl:text> + <xsl:text> }, +</xsl:text> + <xsl:text> on_op_mouse_up_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text>: function(){ +</xsl:text> + <xsl:text> svg_root.removeEventListener("pointerup", this.bound_on_op_mouse_up_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text>, true); +</xsl:text> + <xsl:text> set_activity_state(this.activable_sub_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text>, false); +</xsl:text> + <xsl:text> this.change_hmi_value(0, "</xsl:text> + <xsl:value-of select="func:escape_quotes(@inkscape:label)"/> + <xsl:text>"); +</xsl:text> + <xsl:text> }, +</xsl:text> </xsl:for-each> <xsl:text> init: function() { </xsl:text> @@ -5163,9 +5206,15 @@ <xsl:for-each select="$action_elements"> <xsl:text> this.action_elt_</xsl:text> <xsl:value-of select="position()"/> - <xsl:text>.onclick = () => this.on_op_click("</xsl:text> - <xsl:value-of select="func:escape_quotes(@inkscape:label)"/> - <xsl:text>"); + <xsl:text>.onmousedown = () => this.on_op_mouse_down_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text>(); +</xsl:text> + <xsl:text> this.bound_on_op_mouse_up_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text> = this.on_op_mouse_up_</xsl:text> + <xsl:value-of select="position()"/> + <xsl:text>.bind(this); </xsl:text> </xsl:for-each> <xsl:if test="$have_value"> @@ -10296,6 +10345,8 @@ </xsl:text> <xsl:text> let widget = hmi_widgets[id]; </xsl:text> + <xsl:text> if(widget.curr_value != undefined) return; +</xsl:text> <xsl:text> widget.do_init(); </xsl:text> <xsl:text> }); diff -r 45a66c3fa78a -r 3550841665d3 svghmi/svghmi.py --- a/svghmi/svghmi.py Sun Jan 05 23:12:53 2025 +0100 +++ b/svghmi/svghmi.py Thu Jan 09 18:05:45 2025 +0100 @@ -399,6 +399,18 @@ "tooltip": _("Remove font previously added to HMI"), "method": "_DelFont" }, + { + "bitmap": "AddFile", + "name": _("Add File"), + "tooltip": _("Add file to be served for HMI"), + "method": "_AddFile" + }, + { + "bitmap": "DelFile", + "name": _("Delete File"), + "tooltip": _("Remove file previously added to HMI"), + "method": "_DelFile" + }, ] def _getSVGpath(self, project_path=None): @@ -554,6 +566,21 @@ ctroot.logger.write("SVGHMI:\n") + # To serve user provided static files + # - transfer them as file with a prefixed name + # to avoid potential conflicts + # - generate server code that serve them with + # original name as http path + project_path = self.CTNPath() + static_dir = os.path.join(project_path, "static") + static_files_pairs = [] + if os.path.exists(static_dir): + for fname in os.listdir(static_dir): + undercover_fname = location_str+"_"+fname + static_files_pairs.append('("%s","%s")'%(fname, undercover_fname)) + res += ((undercover_fname, open(os.path.join(static_dir, fname), "rb")),) + static_files = ",\n ".join(static_files_pairs) + if os.path.exists(svgfile): hasher = hashlib.md5() @@ -673,6 +700,10 @@ max_svghmi_sessions = {maxConnections_total} +_{location}_static_files = [ + {static_files} +] + def _runtime_{location}_svghmi_start(): global svghmi_watchdog, svghmi_servers, browser_proc @@ -699,6 +730,10 @@ path_list.append("{path}") + for url_path, file_path in _{location}_static_files: + svghmi_root.putChild(url_path, File(file_path)) + path_list.append(url_path) + browser_proc = {svghmi_cmds[Start]} if {enable_watchdog}: @@ -723,6 +758,10 @@ path_list.remove('{path}') + for url_path, file_path in _{location}_static_files: + svghmi_root.delEntity(url_path) + path_list.remove(url_path) + if len(path_list)==0: svghmi_root.delEntity(b"ws") svghmi_listener.stopListening() @@ -740,6 +779,7 @@ watchdog_interval = self.GetParamsAttributes("SVGHMI.WatchdogInterval")["value"], maxConnections = self.GetParamsAttributes("SVGHMI.MaxConnections")["value"], maxConnections_total = svghmilib.maxConnectionsTotal, + static_files = static_files, **svghmi_options )) @@ -814,6 +854,62 @@ else: self.GetCTRoot().logger.write_error(_("POT file does not exist, add translatable text (label starting with '_') in Inkscape first\n")) + def _AddFile(self): + dialog = wx.FileDialog( + self.GetCTRoot().AppFrame, + _("Choose files so serve"), + os.path.expanduser("~"), + "", + _("Any files (*.*)|*.*"), wx.FD_OPEN) + + if dialog.ShowModal() == wx.ID_OK: + staticfile = dialog.GetPath() + if not os.path.isfile(staticfile): + self.GetCTRoot().logger.write_error( + _('Selected file%s is not a readable file\n')%staticfile) + return + + project_path = self.CTNPath() + + staticfname = os.path.basename(staticfile) + staticdir = os.path.join(project_path, "static") + newstaticfile = os.path.join(staticdir, staticfname) + + if not os.path.exists(staticdir): + os.mkdir(staticdir) + + shutil.copyfile(staticfile, newstaticfile) + + self.GetCTRoot().logger.write( + _('Added file %s as %s\n')%(staticfile,newstaticfile)) + + def _DelFile(self): + project_path = self.CTNPath() + staticdir = os.path.join(project_path, "static") + if not os.path.exists(staticdir) or len(os.listdir(staticdir))==0 : + self.GetCTRoot().logger.write_error( + _("No file in %s\n")%staticdir) + return + dialog = wx.FileDialog( + self.GetCTRoot().AppFrame, + _("Choose a file to remove"), + staticdir, + "", + _("Any files (*.*);*.*"), wx.FD_OPEN) + if dialog.ShowModal() == wx.ID_OK: + staticfile = dialog.GetPath() + if os.path.isfile(staticfile): + if os.path.relpath(staticfile, staticdir) == os.path.basename(staticfile): + os.remove(staticfile) + self.GetCTRoot().logger.write( + _('Removed static file%s\n')%staticfile) + else: + self.GetCTRoot().logger.write_error( + _("StaticFile to remove %s is not in %s\n") % (staticfile,staticdir)) + else: + self.GetCTRoot().logger.write_error( + _("StaticFile file does not exist: %s\n") % staticfile) + def _AddFont(self): dialog = wx.FileDialog( self.GetCTRoot().AppFrame, @@ -852,6 +948,10 @@ def _DelFont(self): project_path = self.CTNPath() fontdir = os.path.join(project_path, "fonts") + if not os.path.exists(fontdir) or len(os.listdir(fontdir))==0 : + self.GetCTRoot().logger.write_error( + _("No font file in %s\n")%fontdir) + return dialog = wx.FileDialog( self.GetCTRoot().AppFrame, _("Choose a font to remove"), diff -r 45a66c3fa78a -r 3550841665d3 svghmi/widget_image.ysl2 --- a/svghmi/widget_image.ysl2 Sun Jan 05 23:12:53 2025 +0100 +++ b/svghmi/widget_image.ysl2 Thu Jan 09 18:05:45 2025 +0100 @@ -28,8 +28,16 @@ | animate: function(){ | this.element.setAttribute('href', this.given_url); | }, - | - | init: function() { - | this.animate(); - | }, } + + +gen_index_xhtml { // following content is only available when generating xhtml file + +// strip inkscape embedded bitmap when it is meant to be replaced by HMI:Image widget +svgtmpl "svg:image[starts-with(@inkscape:label, 'HMI:Image')]", mode="inline_svg" { + xsl:copy { + apply "@*[not(contains(name(), 'href'))] | node()", mode="inline_svg"; + } +} + +} diff -r 45a66c3fa78a -r 3550841665d3 svghmi/widget_input.ysl2 --- a/svghmi/widget_input.ysl2 Sun Jan 05 23:12:53 2025 +0100 +++ b/svghmi/widget_input.ysl2 Thu Jan 09 18:05:45 2025 +0100 @@ -23,9 +23,6 @@ widget_class("Input") || - on_op_click(opstr) { - this.change_hmi_value(0, opstr); - } edit_callback(new_val) { this.apply_hmi_value(0, new_val); } @@ -98,6 +95,22 @@ foreach "$action_elements" { | action_elt_«position()»: id("«@id»"), + const "current_id", "@id"; + const "active", "$hmi_element/*[@id = $current_id]/*[regexp:test(@inkscape:label,'active')]"; + | activable_sub_«position()»: { + foreach "$active" { + | «@inkscape:label»_elt: id("«@id»")`if "position()!=last()" > ,` + } + | }, + | on_op_mouse_down_«position()»: function(){ + | svg_root.addEventListener("pointerup", this.bound_on_op_mouse_up_«position()», true); + | set_activity_state(this.activable_sub_«position()», true); + | }, + | on_op_mouse_up_«position()»: function(){ + | svg_root.removeEventListener("pointerup", this.bound_on_op_mouse_up_«position()», true); + | set_activity_state(this.activable_sub_«position()», false); + | this.change_hmi_value(0, "«func:escape_quotes(@inkscape:label)»"); + | }, } | init: function() { @@ -110,7 +123,8 @@ } foreach "$action_elements" { - | this.action_elt_«position()».onclick = () => this.on_op_click("«func:escape_quotes(@inkscape:label)»"); + | this.action_elt_«position()».onmousedown = () => this.on_op_mouse_down_«position()»(); + | this.bound_on_op_mouse_up_«position()» = this.on_op_mouse_up_«position()».bind(this); } if "$have_value" { diff -r 45a66c3fa78a -r 3550841665d3 tests/projects/mqtt_client/beremiz.xml --- a/tests/projects/mqtt_client/beremiz.xml Sun Jan 05 23:12:53 2025 +0100 +++ b/tests/projects/mqtt_client/beremiz.xml Thu Jan 09 18:05:45 2025 +0100 @@ -1,5 +1,5 @@ <?xml version='1.0' encoding='utf-8'?> <BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="PYRO://localhost:61131" Disable_Extensions="false"> <TargetType/> - <Libraries Enable_Python_Library="false"/> + <Libraries Enable_Python_Library="true" Enable_MQTT_Library="true"/> </BeremizRoot> diff -r 45a66c3fa78a -r 3550841665d3 tests/projects/mqtt_client/plc.xml --- a/tests/projects/mqtt_client/plc.xml Sun Jan 05 23:12:53 2025 +0100 +++ b/tests/projects/mqtt_client/plc.xml Thu Jan 09 18:05:45 2025 +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="Beremiz" productName="Beremiz" productVersion="1" creationDateTime="2016-10-24T18:09:22"/> - <contentHeader name="First Steps" modificationDateTime="2024-07-25T16:55:27"> + <contentHeader name="First Steps" modificationDateTime="2024-12-18T14:47:09"> <coordinateInfo> <fbd> <scaling x="0" y="0"/> @@ -41,6 +41,18 @@ </type> </variable> </localVars> + <localVars> + <variable name="python_eval0"> + <type> + <derived name="python_eval"/> + </type> + </variable> + <variable name="python_eval1"> + <type> + <derived name="python_eval"/> + </type> + </variable> + </localVars> </interface> <body> <FBD> @@ -52,7 +64,7 @@ <expression>LocalVar0</expression> </inVariable> <inVariable localId="3" executionOrderId="0" height="27" width="82" negated="false"> - <position x="126" y="224"/> + <position x="814" y="106"/> <connectionPointOut> <relPosition x="82" y="13"/> </connectionPointOut> @@ -224,11 +236,11 @@ <variable formalParameter="IN0"> <connectionPointIn> <relPosition x="0" y="50"/> - <connection refLocalId="12"> + <connection refLocalId="3"> <position x="956" y="103"/> - <position x="927" y="103"/> - <position x="927" y="115"/> - <position x="898" y="115"/> + <position x="926" y="103"/> + <position x="926" y="119"/> + <position x="896" y="119"/> </connection> </connectionPointIn> </variable> @@ -238,35 +250,201 @@ <connection refLocalId="13"> <position x="956" y="123"/> <position x="933" y="123"/> - <position x="933" y="171"/> - <position x="910" y="171"/> - </connection> - </connectionPointIn> - </variable> - </inputVariables> - <inOutVariables/> - <outputVariables> - <variable formalParameter="OUT"> - <connectionPointOut> - <relPosition x="63" y="30"/> - </connectionPointOut> - </variable> - </outputVariables> - </block> - <inVariable localId="12" executionOrderId="0" height="27" width="34" negated="false"> - <position x="872" y="102"/> + <position x="933" y="159"/> + <position x="910" y="159"/> + </connection> + </connectionPointIn> + </variable> + </inputVariables> + <inOutVariables/> + <outputVariables> + <variable formalParameter="OUT"> + <connectionPointOut> + <relPosition x="63" y="30"/> + </connectionPointOut> + </variable> + </outputVariables> + </block> + <inVariable localId="13" executionOrderId="0" height="27" width="34" negated="false"> + <position x="876" y="146"/> <connectionPointOut> <relPosition x="34" y="13"/> </connectionPointOut> <expression>666</expression> </inVariable> - <inVariable localId="13" executionOrderId="0" height="27" width="34" negated="false"> - <position x="876" y="158"/> - <connectionPointOut> - <relPosition x="34" y="13"/> - </connectionPointOut> - <expression>666</expression> - </inVariable> + <block localId="14" typeName="python_eval" instanceName="python_eval0" executionOrderId="0" height="60" width="98"> + <position x="849" y="235"/> + <inputVariables> + <variable formalParameter="TRIG"> + <connectionPointIn> + <relPosition x="0" y="30"/> + <connection refLocalId="8" formalParameter="OUT"> + <position x="849" y="265"/> + <position x="782" y="265"/> + <position x="782" y="199"/> + <position x="794" y="199"/> + <position x="794" y="95"/> + <position x="784" y="95"/> + </connection> + </connectionPointIn> + </variable> + <variable formalParameter="CODE"> + <connectionPointIn> + <relPosition x="0" y="50"/> + <connection refLocalId="16"> + <position x="849" y="285"/> + <position x="797" y="285"/> + <position x="797" y="291"/> + <position x="745" y="291"/> + </connection> + </connectionPointIn> + </variable> + </inputVariables> + <inOutVariables/> + <outputVariables> + <variable formalParameter="ACK"> + <connectionPointOut> + <relPosition x="98" y="30"/> + </connectionPointOut> + </variable> + <variable formalParameter="RESULT"> + <connectionPointOut> + <relPosition x="98" y="50"/> + </connectionPointOut> + </variable> + </outputVariables> + </block> + <inVariable localId="15" executionOrderId="0" height="27" width="354" negated="false"> + <position x="1384" y="186"/> + <connectionPointOut> + <relPosition x="354" y="13"/> + </connectionPointOut> + <expression>'sys.stdout.write("MQTT PYTHON TEST OK\n")'</expression> + </inVariable> + <inVariable localId="16" executionOrderId="0" height="27" width="218" negated="false"> + <position x="527" y="278"/> + <connectionPointOut> + <relPosition x="218" y="13"/> + </connectionPointOut> + <expression>'publish_example("mhooo")'</expression> + </inVariable> + <block localId="12" typeName="python_eval" instanceName="python_eval1" executionOrderId="0" height="60" width="98"> + <position x="1803" y="146"/> + <inputVariables> + <variable formalParameter="TRIG"> + <connectionPointIn> + <relPosition x="0" y="30"/> + <connection refLocalId="19" formalParameter="OUT"> + <position x="1803" y="176"/> + <position x="1472" y="176"/> + <position x="1472" y="173"/> + <position x="1302" y="173"/> + </connection> + </connectionPointIn> + </variable> + <variable formalParameter="CODE"> + <connectionPointIn> + <relPosition x="0" y="50"/> + <connection refLocalId="15"> + <position x="1803" y="196"/> + <position x="1748" y="196"/> + <position x="1748" y="199"/> + <position x="1738" y="199"/> + </connection> + </connectionPointIn> + </variable> + </inputVariables> + <inOutVariables/> + <outputVariables> + <variable formalParameter="ACK"> + <connectionPointOut> + <relPosition x="98" y="30"/> + </connectionPointOut> + </variable> + <variable formalParameter="RESULT"> + <connectionPointOut> + <relPosition x="98" y="50"/> + </connectionPointOut> + </variable> + </outputVariables> + </block> + <block localId="17" typeName="EQ" executionOrderId="0" height="60" width="63"> + <position x="1144" y="204"/> + <inputVariables> + <variable formalParameter="IN1"> + <connectionPointIn> + <relPosition x="0" y="30"/> + <connection refLocalId="18"> + <position x="1144" y="234"/> + <position x="1114" y="234"/> + <position x="1114" y="235"/> + <position x="1104" y="235"/> + </connection> + </connectionPointIn> + </variable> + <variable formalParameter="IN2"> + <connectionPointIn> + <relPosition x="0" y="50"/> + <connection refLocalId="14" formalParameter="RESULT"> + <position x="1144" y="254"/> + <position x="1063" y="254"/> + <position x="1063" y="285"/> + <position x="947" y="285"/> + </connection> + </connectionPointIn> + </variable> + </inputVariables> + <inOutVariables/> + <outputVariables> + <variable formalParameter="OUT"> + <connectionPointOut> + <relPosition x="63" y="30"/> + </connectionPointOut> + </variable> + </outputVariables> + </block> + <inVariable localId="18" executionOrderId="0" height="27" width="95" negated="false"> + <position x="1009" y="222"/> + <connectionPointOut> + <relPosition x="95" y="13"/> + </connectionPointOut> + <expression>'mhooo'</expression> + </inVariable> + <block localId="19" typeName="AND" executionOrderId="0" height="60" width="63"> + <position x="1239" y="143"/> + <inputVariables> + <variable formalParameter="IN1"> + <connectionPointIn> + <relPosition x="0" y="30"/> + <connection refLocalId="14" formalParameter="ACK"> + <position x="1239" y="173"/> + <position x="990" y="173"/> + <position x="990" y="265"/> + <position x="947" y="265"/> + </connection> + </connectionPointIn> + </variable> + <variable formalParameter="IN2"> + <connectionPointIn> + <relPosition x="0" y="50"/> + <connection refLocalId="17" formalParameter="OUT"> + <position x="1239" y="193"/> + <position x="1223" y="193"/> + <position x="1223" y="234"/> + <position x="1207" y="234"/> + </connection> + </connectionPointIn> + </variable> + </inputVariables> + <inOutVariables/> + <outputVariables> + <variable formalParameter="OUT"> + <connectionPointOut> + <relPosition x="63" y="30"/> + </connectionPointOut> + </variable> + </outputVariables> + </block> </FBD> </body> </pou> diff -r 45a66c3fa78a -r 3550841665d3 tests/projects/mqtt_client/py_ext_0@py_ext/baseconfnode.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/projects/mqtt_client/py_ext_0@py_ext/baseconfnode.xml Thu Jan 09 18:05:45 2025 +0100 @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='utf-8'?> +<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="1" Name="py_ext_0"/> diff -r 45a66c3fa78a -r 3550841665d3 tests/projects/mqtt_client/py_ext_0@py_ext/pyfile.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/projects/mqtt_client/py_ext_0@py_ext/pyfile.xml Thu Jan 09 18:05:45 2025 +0100 @@ -0,0 +1,38 @@ +<?xml version='1.0' encoding='utf-8'?> +<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <variables/> + <globals> + <xhtml:p><![CDATA[ +import sys +last_payload = None + +def topic_callback(topic, payload): + global last_payload + last_payload = payload + +# called from PLC +def publish_example(data): + global last_payload + MQTT_publish("mqtt_0", "py_test", data) + return last_payload + +]]></xhtml:p> + </globals> + <init> + <xhtml:p><![CDATA[ +]]></xhtml:p> + </init> + <cleanup> + <xhtml:p><![CDATA[ +]]></xhtml:p> + </cleanup> + <start> + <xhtml:p><![CDATA[ +MQTT_subscribe("mqtt_0", "py_test", topic_callback) +]]></xhtml:p> + </start> + <stop> + <xhtml:p><![CDATA[ +]]></xhtml:p> + </stop> +</PyFile>