Merge remote-tracking branch 'hggit/python3' into python3
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Thu, 09 Jan 2025 18:05:45 +0100 (2 months ago)
changeset 4089 3550841665d3
parent 4088 45a66c3fa78a (current diff)
parent 4085 8d150133d225 (diff)
child 4090 e0c9ec66a036
Merge remote-tracking branch 'hggit/python3' into python3
--- 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)) 
Binary file exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/static/beremiz.png has changed
Binary file exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/static/file.png has changed
Binary file exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/static/folder.png has changed
--- 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:&quot;&quot;@.filter"
    transform="translate(1380)" /><g
    id="g909-3"
-   inkscape:label="HMI:VarInit:&quot;/files?name=beremiz.png&quot;@.pic"
+   inkscape:label="HMI:VarInit:&quot;beremiz.png&quot;@.pic"
    transform="translate(1380)" />
 
 <g
--- 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"
--- 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'),
Binary file images/AddFile.png has changed
Binary file images/DelFile.png has changed
--- 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>
--- 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
 
--- 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)
--- /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")))
--- 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:
--- 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;
+}}
--- /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>
--- 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)
 
--- 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>
--- 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 = () =&gt; this.on_op_click("</xsl:text>
-      <xsl:value-of select="func:escape_quotes(@inkscape:label)"/>
-      <xsl:text>");
+      <xsl:text>.onmousedown = () =&gt; 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>    });
--- 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"),
--- 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";
+    }
+}
+
+}
--- 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" {
--- 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>
--- 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>
--- /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"/>
--- /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>