<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:discourse="http://discourse.org/rss/modules/discourse/"><channel><title>João Roque on DevBlog</title><link>https://devblog.criticalmanufacturing.com/author/jo%C3%A3o-roque/</link><description>Recent content in João Roque on DevBlog</description><image><url>https://devblog.criticalmanufacturing.com/uploads/og.webp</url><link>https://devblog.criticalmanufacturing.com/uploads/og.webp</link></image><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>A template by [Heksagon](https://www.heksagon.net). Implemented for [Critical Manufacturing](https://www.criticalmanufacturing.com/).</copyright><lastBuildDate>Tue, 30 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://devblog.criticalmanufacturing.com/author/jo%C3%A3o-roque/index.xml" rel="self" type="application/rss+xml"/><item><title>Anatomy of a Script</title><link>https://devblog.criticalmanufacturing.com/blog/20240417_dev_plugins_anatomy_script/</link><pubDate>Wed, 17 Apr 2024 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20240417_dev_plugins_anatomy_script/</guid><description>The lifecycle of a script from head to toes</description><content:encoded><![CDATA[<p>This post is a part of the Learning Lab initiative - <code>[Productivity Month] Boost your Productivity: Unlock your Potential</code> and is a result of the talk <code>[Learning Lab] Task Automation</code>.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview and Motivation</li>
<li>Problem Statement</li>
<li>Using what is Near</li>
<li>Making it a Part of the Whole</li>
</ol>
<h2 id="overview-and-motivation">Overview and Motivation</h2>
<p><em>&ldquo;Sometimes you just need to stop and think.&rdquo;</em></p>
<p>There are several reasons to automate processes, I&rsquo;ll give you mine: &ldquo;This is a waste of time (aka boring)&rdquo;, &ldquo;hum, what did I have to run to do this (*checks digital notes, *then analogue notes, *then under the table)&rdquo; and &ldquo;Ok&hellip; param 1 run <code>ERROR:</code>, ok now again param1 param2 <code>ERROR:</code> ah yeah right param2 param1 run <code>SUCCESS: You just wasted 30mins on this</code>.</p>
<p>The first part of automating processes is understanding what are your processes and workflows. As an engineer you want to keep at the creative and engaging tasks and try to offload what is deterministic and repetitive to a known procedure. This procedure may not even be possible to fully automate, but the more you document and catalog the known path to achieve success, the more you can tweak and improve it and of course automate it.</p>
<p>Scripts are a very dangerous tool, a script when wrongly used is a great way to obfuscate bad practices. A script is not a spell, it shouldn&rsquo;t be a set of wires glued together in order to transform the frog into a princess. It&rsquo;s important to understand the process and the logical steps that you are automating, and sometimes this exercise is great to reduce the noise that exists in our work process. We will show also how to mitigate the second problematic part of script, which I call the pandemic script. This is a script that was done by someone, sometime, somewhere and has been shared a million times and each time it has mutated. Sometimes it mutates into features, sometimes it just mutates into a mutant.</p>
<p>This drive to improve our work and be proactive is a great impulse and has bred a whole tribe in CM &ldquo;Platform Engineering&rdquo;. One of their jobs is to create and help us create amazing tooling that makes our life easier and allows us to focus on the creative work. This also ensures that a script is created in a moment in time to fix one issue, but has a development lifecycle where it is maintained and improved and becomes a proper work tool.</p>
<h2 id="problem-statement">Problem Statement</h2>
<p>The goal for this talk was not to be an abstract presentation on automation, but to show actual helpful examples of things we found that would be or were helpful to automate.</p>
<p>I focused on a use-case that is near to me and that matches three out of three of my criteria to automate. The problem is that in order to be able to launch separately and debug a <code>Connect IoT</code> component (<code>monitor</code>, <code>controller</code> or <code>driver</code>), I am first, required to manually boot each one.</p>
<p>If for example I have a manager with a controller and a driver file-raw:</p>
<ul>
<li>Go to the cache of the automation monitor and for each package that I wanted to reroute to my code source, create a symbolic link with the same name of the package the manager expected. Then I would have to start the components.</li>
</ul>
<h3 id="start-the-automation-monitor">Start the Automation Monitor</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>node ./monitor.js --dev --config=<span style="color:#e6db74">&#39;../config.downloaded.json&#39;</span>  --mp=<span style="color:#ae81ff">88</span>
</span></span></code></pre></div><h3 id="start-the-automation-controller">Start the Automation Controller</h3>
<ul>
<li>In order to start the controllers, I need to see the monitor (or go to the MES) to see what are the Ids the Automation Monitor is expecting and what is the controller package it is expecting.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>node index.js --id=AutomationControllerInstance/<span style="color:#ae81ff">2403060210100000002</span> --monitorPort=<span style="color:#ae81ff">88</span> --config=C:/Users/&lt;user&gt;/Downloads/&lt;manager&gt;/config.downloaded.json --dev=true
</span></span></code></pre></div><h3 id="start-the-automation-driver-for-file-raw">Start the Automation Driver for File-Raw</h3>
<ul>
<li>In order to start the controllers, I need to see the monitor (or go to the MES) to see what are the Ids the Automation Monitor is expecting and what is the controller package it is expecting.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>node index.js --id=AutomationDriverInstance/<span style="color:#ae81ff">2403060210100000004</span> --monitorPort=<span style="color:#ae81ff">88</span> --config=C:/Users/&lt;user&gt;/Downloads/&lt;manager&gt;/config.downloaded.json --dev=true
</span></span></code></pre></div><p><strong>NOTE</strong>: If I had more drivers or more controllers, I would have to start them as well. Of course the one I wanted to test, I would have to launch it from the solution I wanted to debug.</p>
<p>As you can imagine this is a very cumbersome process.</p>
<h2 id="using-what-is-near">Using what is Near</h2>
<p>The easiest way to create scripts that easy to use in Windows is to use the scripting language for Windows Powershell. Powershell has the concept of profile, where you can declare a set of functionality that is loaded whenever you open a shell. For me this is a good starting point as it allows me to have very fast and lean iterations and it&rsquo;s instantly available in the shell without having to install or use third party components.</p>
<p>My first try at it was kind of bad, but that is fine and is part of a proof of concept approach. This is key when trying to automate processes, sometimes it&rsquo;s not clear if automating a process will be easy, and if actually the process that you think is repetitive is actually repetitive and deterministic. So having a first approach to prove that the script works and is usable is helpful for me.</p>
<p>So I added the following script to my profile:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#66d9ef">Function</span> StartAutomationManagerComponents {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">param</span>(
</span></span><span style="display:flex;"><span>        [Parameter(<span style="color:#a6e22e">Mandatory</span> = $true)][<span style="color:#66d9ef">string</span>] $ManagerLocation,
</span></span><span style="display:flex;"><span>        [Parameter(<span style="color:#a6e22e">Mandatory</span> = $true)][<span style="color:#66d9ef">hashtable</span>] $Controller,
</span></span><span style="display:flex;"><span>        [Parameter(<span style="color:#a6e22e">Mandatory</span> = $true)][<span style="color:#66d9ef">hashtable</span>] $Driver,
</span></span><span style="display:flex;"><span>        [Parameter(<span style="color:#a6e22e">Mandatory</span> = $false)][<span style="color:#66d9ef">string</span>] $LinkDir
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Check if I am in a Manager Directory</span>
</span></span><span style="display:flex;"><span>    $path = (Get-Childitem -Path $ManagerLocation -Include package.json -Recurse | Where-Object { $_.FullName <span style="color:#f92672">-notlike</span> <span style="color:#e6db74">&#34;*\node_modules\*&#34;</span> } | Select-Object -First <span style="color:#ae81ff">1</span>).fullName
</span></span><span style="display:flex;"><span>    $name = (Get-Content $path) -join <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">`n</span><span style="color:#e6db74">&#34;</span> | ConvertFrom-Json | Select-Object -ExpandProperty <span style="color:#e6db74">&#34;name&#34;</span>
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> ($name <span style="color:#f92672">-ne</span> <span style="color:#e6db74">&#34;@criticalmanufacturing/connect-iot-manager&#34;</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">throw</span> <span style="color:#e6db74">&#34;You are not in a Directory with an Automation Manager&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Check what is my config</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Order is config.downloaded.json -&gt; config.full.json -&gt; config.json</span>
</span></span><span style="display:flex;"><span>    $config = Get-Childitem -Path $ManagerLocation -Include config.downloaded.json  -Recurse
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (!$config) { Get-Childitem -Path $ManagerLocation -Include config.full.json -Recurse }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (!$config) { Get-Childitem -Path $ManagerLocation -Include config.json -Recurse }
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (!$config) { <span style="color:#66d9ef">throw</span> <span style="color:#e6db74">&#34;Couldn&#39;t find a valid config file&#34;</span> }
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Now we will check for the components in the cache</span>
</span></span><span style="display:flex;"><span>    $cache = (Get-Content $config.FullName) -join <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">`n</span><span style="color:#e6db74">&#34;</span> | ConvertFrom-Json | Select-Object -ExpandProperty <span style="color:#e6db74">&#34;cache&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Validate for Temp</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> ($cache.Contains(<span style="color:#e6db74">&#39;${temp}&#39;</span>)) {
</span></span><span style="display:flex;"><span>        $cache = $cache.Replace(<span style="color:#e6db74">&#39;${temp}&#39;</span>, $env:TEMP) 
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>	
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Make Links</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> ($LinkDir) {
</span></span><span style="display:flex;"><span>        $folders = Get-ChildItem -Path $LinkDir
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    $Monitor = <span style="color:#e6db74">&#34;</span>$($ManagerLocation)<span style="color:#e6db74">/src/&#34;</span> 
</span></span><span style="display:flex;"><span>    $execMonitor = <span style="color:#e6db74">&#34;node monitor.js --dev --config=</span>$config<span style="color:#e6db74"> --mp=88&#34;</span>
</span></span><span style="display:flex;"><span>    $exec = <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    $execControllers = <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    $execDrivers = <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    $exec = <span style="color:#e6db74">&#39;wt --maximized --title &#34;Monitor&#34; -d $Monitor pwsh -c $execMonitor&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    $Controller.keys | ForEach-Object {
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>        $action = <span style="color:#e6db74">&#34;split-pane&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (!$execControllers) {
</span></span><span style="display:flex;"><span>            $action = <span style="color:#e6db74">&#39;new-tab --title &#34;Automation Controllers&#34;&#39;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (!$Controller[$_]) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (!$managerVersion) {
</span></span><span style="display:flex;"><span>                $managerVersion = (Get-Content $path) -join <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">`n</span><span style="color:#e6db74">&#34;</span> | ConvertFrom-Json | Select-Object -ExpandProperty <span style="color:#e6db74">&#34;version&#34;</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            $Controller[$_] = <span style="color:#e6db74">&#34;connect-iot-controller@</span>$managerVersion<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elseif</span> (!$Controller[$_].Contains(<span style="color:#e6db74">&#34;connect-iot-controller&#34;</span>)) {
</span></span><span style="display:flex;"><span>            $Controller[$_] = <span style="color:#e6db74">&#34;connect-iot-controller@</span>$_<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        $value = $Controller[$_]
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Write-Host &#34;Using Controller: $value for instance $_&#34;</span>
</span></span><span style="display:flex;"><span>    		
</span></span><span style="display:flex;"><span>        $packageName = $Controller[$_].Split(<span style="color:#e6db74">&#34;@&#34;</span>)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>        $controllerPath = Get-ChildItem -Path $cache -Include $value -Recurse
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#f92672">-not</span> $controllerPath) { <span style="color:#66d9ef">throw</span> -join (<span style="color:#e6db74">&#34;Expected &#34;</span>, $value, <span style="color:#e6db74">&#34; in &#34;</span>, $cache, <span style="color:#e6db74">&#34; does not exist&#34;</span>) }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> ($folders) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">Foreach</span> ($folder <span style="color:#66d9ef">in</span> $folders) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> ($packageName.Replace(<span style="color:#e6db74">&#34;connect-iot-&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>) <span style="color:#f92672">-eq</span> $folder.Name) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> (Test-Path -Path $controllerPath) {
</span></span><span style="display:flex;"><span>                        Remove-Item $controllerPath  -Force -Recurse
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                    new-item -itemtype symboliclink -path $cache -name $Controller[$_] -value $folder.FullName
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        $execControllers += <span style="color:#e6db74">&#34; </span><span style="color:#ae81ff">``</span><span style="color:#e6db74">; </span>$action<span style="color:#e6db74"> -d </span>$controllerPath<span style="color:#e6db74"> pwsh -c node src/index.js --id=AutomationControllerInstance/</span>$_<span style="color:#e6db74"> --monitorPort=88 --config=</span>$config<span style="color:#e6db74"> --dev=true&#34;</span> 
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    $Driver.keys | ForEach-Object {
</span></span><span style="display:flex;"><span>        $action = <span style="color:#e6db74">&#34;split-pane&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (!$execDrivers) {
</span></span><span style="display:flex;"><span>            $action = <span style="color:#e6db74">&#39;new-tab --title &#34;Automation Drivers&#34;&#39;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (!$Driver[$_].Contains(<span style="color:#e6db74">&#34;connect-iot-&#34;</span>)) {
</span></span><span style="display:flex;"><span>            $Driver[$_] = -join (<span style="color:#e6db74">&#34;connect-iot-&#34;</span>, $Driver[$_])
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (!$Driver[$_].Contains(<span style="color:#e6db74">&#34;@&#34;</span>)) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (!$managerVersion) {
</span></span><span style="display:flex;"><span>                $managerVersion = (Get-Content $path) -join <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">`n</span><span style="color:#e6db74">&#34;</span> | ConvertFrom-Json | Select-Object -ExpandProperty <span style="color:#e6db74">&#34;version&#34;</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            $Driver[$_] = -join ($Driver[$_], <span style="color:#e6db74">&#34;@&#34;</span>, $managerVersion)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        $value = $Driver[$_]
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Write-Host &#34;Using Driver: $value for instance $_&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        $packageName = $value.Split(<span style="color:#e6db74">&#34;@&#34;</span>)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>        $driverPath = Get-Childitem -Path $cache -Include $value -Recurse
</span></span><span style="display:flex;"><span>	
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#f92672">-not</span> $driverPath) { <span style="color:#66d9ef">throw</span> -join (<span style="color:#e6db74">&#34;Expected &#34;</span>, $value, <span style="color:#e6db74">&#34; in &#34;</span>, $cache, <span style="color:#e6db74">&#34; does not exist&#34;</span>) }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> ($folders) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">Foreach</span> ($folder <span style="color:#66d9ef">in</span> $folders) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> ($packageName.Replace(<span style="color:#e6db74">&#34;connect-iot-&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>) <span style="color:#f92672">-eq</span> $folder.Name) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> (Test-Path -Path $driverPath) {
</span></span><span style="display:flex;"><span>                        Remove-Item $driverPath -Force -Recurse
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                    new-item -itemtype symboliclink -path $cache -name $value -value $folder.FullName
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        $execDrivers += <span style="color:#e6db74">&#34; </span><span style="color:#ae81ff">``</span><span style="color:#e6db74">; </span>$action<span style="color:#e6db74"> -d </span>$driverPath<span style="color:#e6db74"> pwsh -c node src/index.js --id=AutomationDriverInstance/</span>$_<span style="color:#e6db74"> --monitorPort=88 --config=</span>$config<span style="color:#e6db74"> --dev=true `;&#34;</span> 
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    $exec = $exec + $execControllers + $execDrivers
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Invoke-Expression $exec
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The code itself it&rsquo;s not important. The important part is that right away we have a lot of repeated code blocks, we have a load of inputs and honestly this could would be very hard to maintain without knowing the use-case very well. But even if all that is true, this script is fast to write and proved that scripting this task was possible and helpful and, importantly, sparked ideas on how it could be changed and remade.</p>
<video class="video-shortcode" preload="auto" controls>
    <source src="/blogPosts/plugins/20240417_dev_plugins_anatomy_script/using_powershell.mp4" type="video/mp4">
    There should have been a video here but your browser does not seem
    to support it.
</video>
<p>Notice that we start the automation manager, we download the default packages. We then kill the automation manager and execute our script. The script will receive the ids of the components and the directory to link the packages. Then it will create an automation workspace.</p>
<h2 id="making-it-a-part-of-the-whole">Making it a Part of the Whole</h2>
<p>Critical Manufacturing already has a structure to incorporate tooling in a way that is simple to use, that is maintainable and easy to share with control throughout the company. This is using the CM CLI, the CLI is something that everyone knows how to use and that we can add our own plugins to. This command is linked to how the development process should be used, so instead of creating a plugin from scratch, which I could have done, I used the existing <code>cmf dev</code> plugin.</p>
<p>The source code for the plugins resides at <a href="https://tfs-product.cmf.criticalmanufacturing.com/Products/Product/_git/DevOps?path=/plugins" target="_blank" rel="noopener">Dev Ops Plugins</a>.</p>
<p>Looking at the source code it&rsquo;s easy to organize and orientate our script in this tool set mindset. I want to be able to just run <code>cmf dev iot startComponents</code>.</p>
<p>
<img src="/blogPosts/plugins/20240417_dev_plugins_anatomy_script/plugin_source_code.png" alt="Plugins Source Code" />

</p>
<p>In order to do this in the <code>dev</code> project we will create a folder to hold our <code>iot</code> related dev commands and two cs files. Our parent command <code>IoTCommand</code> and our command <code>StartComponentsCommand</code>;</p>
<p>The <code>IoTCommand</code> is a parent command so it&rsquo;s very simple, it serves just to aggregate commands. Notice the attributes in the method header, those will be important to map to the command console execution.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#a6e22e">[CmfCommand(&#34;iot&#34;, Id = &#34;dev_iot&#34;)]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">IoTCommand</span> : BaseCommand
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> IoTCommand() : <span style="color:#66d9ef">this</span>(<span style="color:#66d9ef">new</span> FileSystem())
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> IoTCommand(IFileSystem fileSystem) : <span style="color:#66d9ef">base</span>(fileSystem)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        PluginExecutionContext.Initialize(<span style="color:#66d9ef">this</span>.fileSystem);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>(...)
</span></span></code></pre></div><p>Now, our actual command. A command is divided into two parts: the <code>Configure</code>, where we treat and prepare all the arguments that will be used by the command and the <code>Execute</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#a6e22e">[CmfCommand(&#34;startComponents&#34;, Id = &#34;iot_startComponents&#34;, ParentId = &#34;dev_iot&#34;)]</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">StartComponentsCommand</span> : IoTCommand
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/// &lt;summary&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/// Configure command</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/// &lt;/summary&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/// &lt;param name=&#34;cmd&#34;&gt;&lt;/param&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">override</span> <span style="color:#66d9ef">void</span> Configure(Command cmd)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        cmd.AddArgument(<span style="color:#66d9ef">new</span> Argument&lt;IDirectoryInfo&gt;(
</span></span><span style="display:flex;"><span>            name: <span style="color:#e6db74">&#34;managerLocation&#34;</span>,
</span></span><span style="display:flex;"><span>            parse: argResult =&gt; Parse&lt;IDirectoryInfo&gt;(argResult)
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            Description = <span style="color:#e6db74">&#34;Location of your downloaded manager&#34;</span>
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        cmd.AddOption(<span style="color:#66d9ef">new</span> Option&lt;IDirectoryInfo&gt;(
</span></span><span style="display:flex;"><span>            aliases: <span style="color:#66d9ef">new</span> <span style="color:#66d9ef">string</span>[] { <span style="color:#e6db74">&#34;--linkDir&#34;</span>, <span style="color:#e6db74">&#34;-ld&#34;</span> },
</span></span><span style="display:flex;"><span>            parseArgument: argResult =&gt; Parse&lt;IDirectoryInfo&gt;(argResult),
</span></span><span style="display:flex;"><span>            description: <span style="color:#e6db74">$&#34;This specifies the directory you wish to use to create links in order to debug or to use your code.&#34;</span>));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        cmd.AddOption(<span style="color:#66d9ef">new</span> Option&lt;<span style="color:#66d9ef">string</span>&gt;(
</span></span><span style="display:flex;"><span>            aliases: <span style="color:#66d9ef">new</span> <span style="color:#66d9ef">string</span>[] { <span style="color:#e6db74">&#34;--linkVersion&#34;</span>, <span style="color:#e6db74">&#34;-lv&#34;</span> },
</span></span><span style="display:flex;"><span>            description: <span style="color:#e6db74">$&#34;Specify version that will be used for the link packages, to allow using a linked package with a different version that the one defined for the runtime (note: will be used if there are no packages in the cache)&#34;</span>));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Add the handler</span>
</span></span><span style="display:flex;"><span>        cmd.Handler = CommandHandler.Create&lt;IDirectoryInfo, IDirectoryInfo, <span style="color:#66d9ef">string</span>&gt;(Execute);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>(...)
</span></span></code></pre></div><p>Notice how the tooling already does most of the heavy-lifting, it already allows to differentiate between optional and mandatory arguments, allows to add helpful descriptions for the user and the use of aliases. We have one mandatory arguments that is the manager location and two optional arguments, the directory where we want to link our cache and in the case of an empty cache the link version we want to give to the symbolic links.</p>
<p>Let&rsquo;s take a look at the execution phase. The first phase is validation, we will validate the correct operating system, then we validate that the manager location is actually a manager location and we will replace a very common token in the config, by a location the filesystem understands. Also, we will interpret the configs with the correct order of precedence.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>(...)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/// &lt;summary&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/// Executes the specified target</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/// &lt;/summary&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/// &lt;exception cref=&#34;PluginException&#34;&gt;&lt;/exception&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">void</span> Execute(IDirectoryInfo managerLocation, IDirectoryInfo linkDir, <span style="color:#66d9ef">string</span> linkVersion)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">using</span> var activity = ExecutionContext.ServiceProvider?.GetService&lt;ITelemetryService&gt;()?.StartExtendedActivity(<span style="color:#66d9ef">this</span>.GetType().Name);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (!OperatingSystem.IsWindows())
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> PluginException(Cmf.CLI.Plugin.Common.Messages.OSNotSupported);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        IFileInfo configJson;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span>
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Check if directory is a Manager Directory</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">dynamic</span> packageJson = managerLocation.GetFile(<span style="color:#e6db74">&#34;package.json&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (packageJson == <span style="color:#66d9ef">null</span> || packageJson.name != <span style="color:#e6db74">&#34;@criticalmanufacturing/connect-iot-manager&#34;</span>)
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> PluginException(<span style="color:#e6db74">$&#34;Manager Location either does not contain a package.json or the packagen.json does not match the expected package @criticalmanufacturing/connect-iot-manager&#34;</span>);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Check config to use:</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Start with downloaded -&gt; full -&gt; config.json</span>
</span></span><span style="display:flex;"><span>            configJson = managerLocation.GetFiles(<span style="color:#e6db74">&#34;config.downloaded.json&#34;</span>).FirstOrDefault();
</span></span><span style="display:flex;"><span>            configJson ??= managerLocation.GetFiles(<span style="color:#e6db74">&#34;config.full.json&#34;</span>).FirstOrDefault();
</span></span><span style="display:flex;"><span>            configJson ??= managerLocation.GetFiles(<span style="color:#e6db74">&#34;config.json&#34;</span>).FirstOrDefault();
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">catch</span>
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> PluginException(<span style="color:#e6db74">$&#34;This Directory {managerLocation} does not contain an Automation Manager&#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Read the config.json to see where the package dependencies are</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">dynamic</span> configJsonContent = JsonConvert.DeserializeObject(configJson.ReadToString());
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">string</span> cacheLocation = configJsonContent.cache.ToString();
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Replace temp token with temp location</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (cacheLocation.Contains(<span style="color:#e6db74">&#34;${temp}&#34;</span>))
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            cacheLocation = cacheLocation.Replace(<span style="color:#e6db74">&#34;${temp}&#34;</span>, Environment.GetEnvironmentVariable(<span style="color:#e6db74">&#34;TEMP&#34;</span>));
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>(...)
</span></span></code></pre></div><p>The next phase is preparing the links. If a link directory is passed, we must change the cached package names and create symbolic links to the packages in the directory link. If there are packages in the cache we will match those with the ones in the directory link and perform just the link for those. If the cache is empty, we will link all the packages, here we can either link them with their own version or use the linked version argument version.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>(...)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#region Prepare Links</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Check if I have to do links</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (linkDir != <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            IDirectoryInfo cache = <span style="color:#66d9ef">this</span>.fileSystem.DirectoryInfo.New(cacheLocation);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">var</span> cachedPackages = cache.GetDirectories();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// If there are packages we will link the ones that match, else we will link all</span>
</span></span><span style="display:flex;"><span>            Dictionary&lt;<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">string</span>&gt; packagesToLink = <span style="color:#66d9ef">new</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (cachedPackages != <span style="color:#66d9ef">null</span> &amp;&amp; cachedPackages.Any())
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">foreach</span> (<span style="color:#66d9ef">var</span> cachedPackage <span style="color:#66d9ef">in</span> cachedPackages)
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// if the package as already processed or if it us a link we will skip it</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> (cachedPackage.LinkTarget != <span style="color:#66d9ef">null</span> || cachedPackage.Name.Contains(<span style="color:#e6db74">&#34;_CACHE_ORIGINAL&#34;</span>))
</span></span><span style="display:flex;"><span>                    {
</span></span><span style="display:flex;"><span>                        Log.Debug(<span style="color:#e6db74">$&#34;This packaged is already a link, so it will not be relinked {cachedPackage.Name}&#34;</span>);
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">continue</span>;
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// We will go to each cached package and see the name and the version of the package</span>
</span></span><span style="display:flex;"><span>                    IFileInfo cachedPackageJson = cachedPackage.GetFiles(<span style="color:#e6db74">&#34;package.json&#34;</span>).FirstOrDefault();
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">dynamic</span> content = JsonConvert.DeserializeObject(cachedPackageJson.ReadToString());
</span></span><span style="display:flex;"><span>                    packagesToLink.Add(content.name.ToString(), content.version.ToString());
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Check matches between cache and folders in the directory</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Rename existing folders to _CACHE_ORIGINAL_</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Make Links</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">var</span> linkedDirectories = linkDir.GetDirectories();
</span></span><span style="display:flex;"><span>            List&lt;IDirectoryInfo&gt; createdLinkedDirectories = <span style="color:#66d9ef">new</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">foreach</span> (<span style="color:#66d9ef">var</span> linkedDirectory <span style="color:#66d9ef">in</span> linkedDirectories)
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                IFileInfo linkedPackageJson = linkedDirectory.GetFiles(<span style="color:#e6db74">&#34;package.json&#34;</span>).FirstOrDefault();
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">dynamic</span> content = JsonConvert.DeserializeObject(linkedPackageJson.ReadToString());
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// We will check if the package name exists in the list of cached packages</span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Or if there are no cached packages in that case we link all</span>
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (!packagesToLink.Any() || packagesToLink.ContainsKey(content.name.ToString()))
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// Remove scope</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">var</span> packageVersion = packagesToLink.Any() ? packagesToLink[content.name.ToString()] : linkVersion ?? content.version.ToString();
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">var</span> packageName = <span style="color:#e6db74">$&#34;{content.name.ToString().Split(&#34;</span>/<span style="color:#e6db74">&#34;)[1]}@{packageVersion}&#34;</span>;
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">var</span> dirName = Path.Join(cache.FullName, packageName);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// If it&#39;s a downloaded package we will rename it</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// Else we will just delete it</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> (packagesToLink.Any()) { <span style="color:#66d9ef">this</span>.fileSystem.Directory.Move(dirName, dirName + <span style="color:#e6db74">&#34;_CACHE_ORIGINAL&#34;</span>); }
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">var</span> directory = <span style="color:#66d9ef">this</span>.fileSystem.DirectoryInfo.New(dirName);
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> (directory.Exists) { directory.Delete(); }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// Create a Link between the cache and our package with the correct name &lt;packageName&gt;@&lt;cachedPackageVersion&gt;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">this</span>.fileSystem.Directory.CreateSymbolicLink(dirName, linkedDirectory.FullName);
</span></span><span style="display:flex;"><span>                    createdLinkedDirectories.Add(<span style="color:#66d9ef">this</span>.fileSystem.DirectoryInfo.New(dirName));
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#960050;background-color:#1e0010">#</span>endregion
</span></span><span style="display:flex;"><span>(...)
</span></span></code></pre></div><p>I really wanted to avoid the need for me to know the ids of the components and the arguments to start them. So I decided a different approach, I start the automation monitor for a dummy execution and sniff the output of the console for the parts where the monitor tells me what I need to start. Then I kill the monitor and perform a clean start. If you are curious for the messages being intercepted, try to start only the monitor and see what it logs, and you will see the messages we are intercepting. We wait a set amount of time for the monitor to finish and then kill the process.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>(...)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#region Dummy Execution Phase</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Start Monitor</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Intercept Messages regarding Drivers and Controllers</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// We will sniff the console output of the monitor and see what are the components that must be started and their Ids and packages</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Then the monitor we will kill the monitor and start everything</span>
</span></span><span style="display:flex;"><span>        CommandLineRunner runMonitor = <span style="color:#66d9ef">new</span>(fileSystem.DirectoryInfo.New(Path.Join(managerLocation.FullName, <span style="color:#e6db74">&#34;src&#34;</span>)));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        List&lt;ComponentExecution&gt; componentExecutions = <span style="color:#66d9ef">new</span> List&lt;ComponentExecution&gt;();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">string</span> idInForkPattern = <span style="color:#e6db74">&#34;(?&lt;=id=&#39;)[^&#39;]+&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">string</span> pathInForkPattern = <span style="color:#e6db74">&#34;(?&lt;=path=&#39;)[^&#39;]+&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">string</span> idInArgumentsPattern = <span style="color:#e6db74">&#34;(?&lt;=--id=)(.*?)(?= --managerId)&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">string</span> argumentsInArgumentsPattern = <span style="color:#e6db74">&#34;--id=(.*)&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">bool</span> killedMonitor = <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>        Action&lt;<span style="color:#66d9ef">string</span>&gt; monitorConsoleHandler = output =&gt;
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (output != <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// In this Message we will know the Id of the component and the package it needs to boot</span>
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (output.Contains(<span style="color:#e6db74">&#34;Fork requested *NOT* executed&#34;</span>))
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">var</span> id = Regex.Match(output, idInForkPattern).Value;
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">var</span> path = Regex.Match(output, pathInForkPattern).Value;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    componentExecutions.Add(<span style="color:#66d9ef">new</span> ComponentExecution(id, path));
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// In this Message we will know the arguments required to execute the component</span>
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> (output.Contains(<span style="color:#e6db74">&#34;Arguments: &#34;</span>))
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">var</span> id = Regex.Match(output, idInArgumentsPattern).Value;
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">var</span> arguments = Regex.Match(output, idInArgumentsPattern).Value;
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">var</span> comp = componentExecutions.FirstOrDefault(x =&gt; x.Id == id);
</span></span><span style="display:flex;"><span>                    comp.Arguments = Regex.Match(output, argumentsInArgumentsPattern).Value;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> (output.Contains(<span style="color:#e6db74">&#34;error&#34;</span>))
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    runMonitor.KillProcess();
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> PluginException(<span style="color:#e6db74">$&#34;Something went wrong when booting up the Monitor, please fix it before running the command again - {output}&#34;</span>);
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> (output.Contains(<span style="color:#e6db74">&#34;Processes and packages processed&#34;</span>))
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// When all packages are processed we will kill the Monitor Process that we started</span>
</span></span><span style="display:flex;"><span>                    runMonitor.KillProcess();
</span></span><span style="display:flex;"><span>                    killedMonitor = <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Grab a random port in the 81-90 range</span>
</span></span><span style="display:flex;"><span>        Random random = <span style="color:#66d9ef">new</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">int</span> randomPort = random.Next(<span style="color:#ae81ff">81</span>, <span style="color:#ae81ff">90</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> monitorExec = <span style="color:#e6db74">$&#34;node {Path.Join(managerLocation.FullName, &#34;</span>src<span style="color:#e6db74">&#34;, &#34;</span>monitor.js<span style="color:#e6db74">&#34;)} --dev --config={configJson.FullName} --mp={randomPort}&#34;</span>;
</span></span><span style="display:flex;"><span>        runMonitor.Run(monitorExec, waitForExit: <span style="color:#66d9ef">true</span>, outputHandler: monitorConsoleHandler, logOutputOnError: <span style="color:#66d9ef">false</span>, errorCodeToIgnore: -<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// We will wait a set amount of time for the monitor process to log all components</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span>
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            WaitFor(<span style="color:#ae81ff">60</span>, () =&gt;
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (killedMonitor)
</span></span><span style="display:flex;"><span>                {
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// Wait for the monitor to finish</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>            }, <span style="color:#e6db74">&#34;No Fork was Requested&#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">catch</span> (Exception)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            runMonitor.KillProcess();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> PluginException(<span style="color:#e6db74">&#34;No Fork was Requested from monitor in the allotted 60s period&#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        Log.Debug(<span style="color:#e6db74">$&#34;Number of Components found - {componentExecutions.Count}&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#960050;background-color:#1e0010">#</span>endregion
</span></span><span style="display:flex;"><span>(...)
</span></span></code></pre></div><p>In the last phase we construct the windows terminal workspace, similar to what we were already doing in the script.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>(...)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">#region Run All Components</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        CommandLineRunner runAll = <span style="color:#66d9ef">new</span>(fileSystem.DirectoryInfo.New(Path.Join(managerLocation.FullName, <span style="color:#e6db74">&#34;src&#34;</span>)));
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> exec = <span style="color:#e6db74">$&#34;wt --maximized --title \&#34;</span>Monitor<span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">&#34; pwsh -c {monitorExec}&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// For each component type we will open a new tab and for each component of the same type we will spit the tab</span>
</span></span><span style="display:flex;"><span>        ComponentType? previousType = <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">foreach</span> (<span style="color:#66d9ef">var</span> component <span style="color:#66d9ef">in</span> componentExecutions.OrderBy(x =&gt; x.Type))
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">var</span> action = <span style="color:#e6db74">&#34;split-pane&#34;</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (previousType != component.Type)
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                action = <span style="color:#e6db74">$&#34;new-tab --title \&#34;</span>Automation {component.Type}<span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">&#34;&#34;</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            exec += <span style="color:#e6db74">$&#34; ``; {action} pwsh -c node {component.Path} {component.Arguments}&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            previousType = component.Type;
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Controller - Collect Ids and Package versions</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Driver - Collect Ids, Package Names and Versions</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Start Controllers and Drivers</span>
</span></span><span style="display:flex;"><span>        Log.Debug(<span style="color:#e6db74">$&#34;Executing Command - {exec}&#34;</span>);
</span></span><span style="display:flex;"><span>        runAll.Run(exec, waitForExit: <span style="color:#66d9ef">false</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#960050;background-color:#1e0010">#</span>endregion
</span></span><span style="display:flex;"><span>(...)
</span></span></code></pre></div><p>In order to execute our command</p>
<p>To just start the components:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>cmf dev iot startComponents &lt;managerlocation&gt;
</span></span></code></pre></div><p>To start the components and make links, with an existing cache:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>cmf dev iot startComponents &lt;managerlocation&gt; -ld &lt;linkDirectory&gt;
</span></span></code></pre></div><p>To start the components and make links, without an existing cache, but with a specific version:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>cmf dev iot startComponents &lt;managerlocation&gt; -ld &lt;linkDirectory&gt; -lv &lt;linkVersion&gt;
</span></span></code></pre></div><p>With this we finish the loop, we started with an idea on how to improve a part of our workflow that was very cumbersome and prone to error and now we have a code block with head, torso and members, this way closing the anatomy of a script.</p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Generic Package to Deploy TGZ packages</title><link>https://devblog.criticalmanufacturing.com/blog/20240417_customization_package_tgz/</link><pubDate>Wed, 17 Apr 2024 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20240417_customization_package_tgz/</guid><description>How to deploy tgz packages using Generic packages.</description><content:encoded><![CDATA[<p>This post is to illustrate how we can use a generic package to deploy tgz packages.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview and Motivation</li>
<li>Generic Package</li>
</ol>
<h2 id="overview-and-motivation">Overview and Motivation</h2>
<p>It is very common to want to deploy a customization package that is based not on a full customization projects, but holds only some prepacked artifacts. For example when we want to use pre-release versions of product packages but don&rsquo;t want to include the product source code in our project.</p>
<p>In this example I wanted to generate a deployable package outside of a project scaffolding. This example is mostly valid for Connect IoT / Factory Automation packages, but it can be tweaked for other type of artifacts.</p>
<h2 id="generic-package">Generic Package</h2>
<p>Let&rsquo;s first create our root package. A root package requires only the existence of a <code>cmfpackage.json</code> that describes this as a root package. We will also add an entry to our package that will hold the tgz files.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;packageId&#34;</span>: <span style="color:#e6db74">&#34;Cmf.Custom.DeployTGZ&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;version&#34;</span>: <span style="color:#e6db74">&#34;1.0.0&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;Package root for deploying TGZ&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;packageType&#34;</span>: <span style="color:#e6db74">&#34;Root&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;isInstallable&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;isUniqueInstall&#34;</span>: <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;dependencies&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;Cmf.Environment&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;version&#34;</span>: <span style="color:#e6db74">&#34;&lt;%= $CLI_PARAM_MESVersion %&gt;&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;mandatory&#34;</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;Cmf.Custom.DeployTGZ.Packages&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;version&#34;</span>: <span style="color:#e6db74">&#34;&lt;%= $CLI_PARAM_MESVersion %&gt;&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;mandatory&#34;</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now lets create a folder to hold our generic package. Let&rsquo;s create a new <code>cmfpackage.json</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;packageId&#34;</span>: <span style="color:#e6db74">&#34;Cmf.Custom.DeployTGZ.Packages&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;version&#34;</span>: <span style="color:#e6db74">&#34;1.0.0&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;Package to deploy TGZ&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;packageType&#34;</span>: <span style="color:#e6db74">&#34;Generic&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;isInstallable&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;isUniqueInstall&#34;</span>: <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;targetDirectory&#34;</span>: <span style="color:#e6db74">&#34;UI/Html&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;targetLayer&#34;</span>: <span style="color:#e6db74">&#34;ui&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;contentToPack&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;source&#34;</span>: <span style="color:#e6db74">&#34;projects/*.tgz&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;target&#34;</span>: <span style="color:#e6db74">&#34;runtimePackages&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;ignoreFiles&#34;</span>: [
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;.npmignore&#34;</span>
</span></span><span style="display:flex;"><span>      ]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;xmlInjection&#34;</span>: [
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;steps.xml&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;ui.xml&#34;</span>
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The generic package allows us to configure the build steps and steps to install as we wish. In this case we will not have build steps, but we will inject our deployment steps, in order to deploy the tgz files to the connect iot repository. The content to pack will dictate that the tgz files will be in the <code>projects/</code> directory and will be deployed to a folder called runtimePackages. Then we will add a set of injectable steps, these will be joined and parsed to create the deployment metadata.</p>
<p>The ui xml will emulate the devops center options and steps in the GUI.</p>
<p>ui.xml:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#f92672">&lt;ui&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;wizardStep</span> <span style="color:#a6e22e">id=</span><span style="color:#e6db74">&#34;Deployment.ConnectIoT&#34;</span> <span style="color:#a6e22e">order=</span><span style="color:#e6db74">&#34;100&#34;</span> <span style="color:#a6e22e">title=</span><span style="color:#e6db74">&#34;IoT Configuration&#34;</span> <span style="color:#a6e22e">type=</span><span style="color:#e6db74">&#34;generic&#34;</span> <span style="color:#a6e22e">requiresValidation=</span><span style="color:#e6db74">&#34;true&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;groups&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;group</span> <span style="color:#a6e22e">id=</span><span style="color:#e6db74">&#34;Deployment.Steps.ConnectIoT.Directory&#34;</span> <span style="color:#a6e22e">order=</span><span style="color:#e6db74">&#34;0&#34;</span> <span style="color:#a6e22e">title=</span><span style="color:#e6db74">&#34;DIRECTORY REPOSITORY&#34;</span> <span style="color:#a6e22e">type=</span><span style="color:#e6db74">&#34;generic&#34;</span> <span style="color:#a6e22e">requiresValidation=</span><span style="color:#e6db74">&#34;false&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>	  <span style="color:#f92672">&lt;variables&gt;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">&lt;variable</span> <span style="color:#a6e22e">name=</span><span style="color:#e6db74">&#34;Local.Repository.Directory.Enabled&#34;</span> <span style="color:#a6e22e">readOnly=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">isRequired=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">valueType=</span><span style="color:#e6db74">&#34;Boolean&#34;</span> <span style="color:#a6e22e">label=</span><span style="color:#e6db74">&#34;Is Enabled&#34;</span> <span style="color:#a6e22e">default=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">&lt;variable</span> <span style="color:#a6e22e">name=</span><span style="color:#e6db74">&#34;Local.Repository.Directory.Location&#34;</span> <span style="color:#a6e22e">readOnly=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">isRequired=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">valueType=</span><span style="color:#e6db74">&#34;Text&#34;</span> <span style="color:#a6e22e">label=</span><span style="color:#e6db74">&#34;Location&#34;</span> <span style="color:#a6e22e">placeholder=</span><span style="color:#e6db74">&#34;Example: C:\repositoryLocation&#34;</span> <span style="color:#a6e22e">default=</span><span style="color:#e6db74">&#34;C:\repositoryLocation&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>	  <span style="color:#f92672">&lt;/variables&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;/group&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;group</span> <span style="color:#a6e22e">id=</span><span style="color:#e6db74">&#34;Deployment.Steps.ConnectIoT.Server&#34;</span> <span style="color:#a6e22e">order=</span><span style="color:#e6db74">&#34;1&#34;</span> <span style="color:#a6e22e">title=</span><span style="color:#e6db74">&#34;NPM SERVER REPOSITORY&#34;</span> <span style="color:#a6e22e">type=</span><span style="color:#e6db74">&#34;generic&#34;</span> <span style="color:#a6e22e">requiresValidation=</span><span style="color:#e6db74">&#34;false&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>	  <span style="color:#f92672">&lt;variables&gt;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">&lt;variable</span> <span style="color:#a6e22e">name=</span><span style="color:#e6db74">&#34;Local.Repository.Server.Enabled&#34;</span> <span style="color:#a6e22e">readOnly=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">isRequired=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">valueType=</span><span style="color:#e6db74">&#34;Boolean&#34;</span> <span style="color:#a6e22e">label=</span><span style="color:#e6db74">&#34;Is Enabled&#34;</span> <span style="color:#a6e22e">default=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">&lt;variable</span> <span style="color:#a6e22e">name=</span><span style="color:#e6db74">&#34;Local.Repository.Server.Address&#34;</span> <span style="color:#a6e22e">readOnly=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">isRequired=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">valueType=</span><span style="color:#e6db74">&#34;Text&#34;</span> <span style="color:#a6e22e">label=</span><span style="color:#e6db74">&#34;Address&#34;</span> <span style="color:#a6e22e">placeholder=</span><span style="color:#e6db74">&#34;Example: http://127.0.0.1:4873&#34;</span> <span style="color:#a6e22e">default=</span><span style="color:#e6db74">&#34;http://127.0.0.1:4873&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">&lt;variable</span> <span style="color:#a6e22e">name=</span><span style="color:#e6db74">&#34;Publish.Tag&#34;</span> <span style="color:#a6e22e">readOnly=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">isRequired=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">valueType=</span><span style="color:#e6db74">&#34;Text&#34;</span> <span style="color:#a6e22e">label=</span><span style="color:#e6db74">&#34;Tag&#34;</span> <span style="color:#a6e22e">placeholder=</span><span style="color:#e6db74">&#34;Example: latest&#34;</span> <span style="color:#a6e22e">default=</span><span style="color:#e6db74">&#34;latest&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">&lt;variable</span> <span style="color:#a6e22e">name=</span><span style="color:#e6db74">&#34;Local.Repository.Server.UserName&#34;</span> <span style="color:#a6e22e">readOnly=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">isRequired=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">valueType=</span><span style="color:#e6db74">&#34;Text&#34;</span> <span style="color:#a6e22e">label=</span><span style="color:#e6db74">&#34;Registry User&#34;</span> <span style="color:#a6e22e">placeholder=</span><span style="color:#e6db74">&#34;Example: admin&#34;</span> <span style="color:#a6e22e">default=</span><span style="color:#e6db74">&#34;admin&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">&lt;variable</span> <span style="color:#a6e22e">name=</span><span style="color:#e6db74">&#34;Local.Repository.Server.UserPassword&#34;</span> <span style="color:#a6e22e">readOnly=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">isRequired=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">valueType=</span><span style="color:#e6db74">&#34;Password&#34;</span> <span style="color:#a6e22e">label=</span><span style="color:#e6db74">&#34;Registry User Password&#34;</span> <span style="color:#a6e22e">placeholder=</span><span style="color:#e6db74">&#34;Example: password&#34;</span> <span style="color:#a6e22e">default=</span><span style="color:#e6db74">&#34;password&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">&lt;variable</span> <span style="color:#a6e22e">name=</span><span style="color:#e6db74">&#34;Local.Repository.Server.UserEmail&#34;</span> <span style="color:#a6e22e">readOnly=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">isRequired=</span><span style="color:#e6db74">&#34;false&#34;</span> <span style="color:#a6e22e">valueType=</span><span style="color:#e6db74">&#34;Text&#34;</span> <span style="color:#a6e22e">label=</span><span style="color:#e6db74">&#34;Registry User Email&#34;</span> <span style="color:#a6e22e">placeholder=</span><span style="color:#e6db74">&#34;Example: admin@companyname.com&#34;</span> <span style="color:#a6e22e">default=</span><span style="color:#e6db74">&#34;admin@companyname.com&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>	  <span style="color:#f92672">&lt;/variables&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;/group&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;/groups&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/wizardStep&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/ui&gt;</span>
</span></span></code></pre></div><p>The steps xml will be responsible to determine the actions to be performed. In this case we want not only to deploy but also to generate the repository index.</p>
<p>steps.xml:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span>  <span style="color:#f92672">&lt;steps&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">&lt;!-- ONLY VALID For VM - Will be ignored in a container installation
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    &lt;step type=&#34;DeployFiles&#34; contentPath=&#34;runtimePackages/**&#34; /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    &lt;step type=&#34;DeployFiles&#34; contentPath=&#34;*.ps1&#34; /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    &lt;step type=&#34;DeployFiles&#34; contentPath=&#34;*.bat&#34; /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    &lt;step type=&#34;Generic&#34; onExecute=&#34;$(Package[Cmf.Custom.DeployTGZ.Packages].TargetDirectory)/runtimePackages/ValidateIoTInstall.ps1&#34; /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    &lt;step type=&#34;Generic&#34; onExecute=&#34;$(Package[Cmf.Custom.DeployTGZ.Packages].TargetDirectory)/runtimePackages/PublishToRepository.ps1&#34; /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    &lt;step type=&#34;Generic&#34; onExecute=&#34;$(Package[Cmf.Custom.DeployTGZ.Packages].TargetDirectory)/runtimePackages/PublishToDirectory.ps1&#34; /&gt; 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    --&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;step</span> <span style="color:#a6e22e">type=</span><span style="color:#e6db74">&#34;DeployRepositoryFiles&#34;</span> <span style="color:#a6e22e">contentPath=</span><span style="color:#e6db74">&#34;runtimePackages/**&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;step</span> <span style="color:#a6e22e">type=</span><span style="color:#e6db74">&#34;GenerateRepositoryIndex&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;/steps&gt;</span>
</span></span></code></pre></div><p><a href="/blogPosts/plugins/20240417_customization_package_tgz/Cmf.Custom.DeployTGZ.zip">Download Example Package</a></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Gutenberg - Atomic Habits</title><link>https://devblog.criticalmanufacturing.com/blog/20240328_gutenberg_atomichabits/</link><pubDate>Thu, 28 Mar 2024 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20240328_gutenberg_atomichabits/</guid><description>Atomic Habits</description><content:encoded><![CDATA[<p>This post will introduce the talk that we had by David Sora about the book Atomic Habits. The <code>Gutenberg</code> library, now has a big learning space with more that 70 books.</p>
<h2 id="presenter">Presenter</h2>
<p>We had an amazing talk by our very own David Sora. He is one of the most driven and focused members of Critical Manufacturing. So it was no surprise to find he was an avid reader. He was challenged with presenting a book that made him be better. He choose one of the most requested books in the <code>Gutenberg</code> library, <code>Atomic Habits</code>.</p>
<p>
<figure class="text-center">
  <img
    src="/blogPosts/contributors/presenter_david_sora.jpg"
    alt="David Sora"
    title="Head of Performance and Management Control"
  />
  <figcaption style="text-align: center">Head of Performance and Management Control</figcaption>
</figure>

</p>
<h2 id="book---atomic-habits">Book - Atomic Habits</h2>
<p>
<img src="/blogPosts/gutenberg/20240328_gutenberg_atomichabits/atomic_habits.jpg" alt="Atomic Habits" />

</p>
<h3 id="walkthrough">Walkthrough</h3>
<p>David gave us a vivid account of the impact of the book on his own life and at the same time walking us step by step in the main concepts and ideas of the book. The major impact of small and incremental changes is a big eye opener. Everyone that is trying to build good habits and kick off bad habits should definitely grab this book.</p>
<p>The idea of compound interest and that a 1% improvement generates a 37x improvement after just one year, is a unbeatable argument to nay sayers. The contrast between the proof / identity methods is also a great brain poke to everyone that wants to understand the construction of the habit.</p>
<p>The big lifecycle of <code>Cue -&gt; Desire -&gt; Reaction -&gt; Reward</code> is very important to understand ourselves and our relation between what we do and why we do it.</p>
<p>
<img src="/blogPosts/gutenberg/20240328_gutenberg_atomichabits/presentation.jpg" alt="Library - Presentation" />

</p>
<p>It was not just a presentation we also had a big Give Away of four <code>Atomic Habits</code> books and a big lunch!</p>
<p>This was an awesome way to kick-off the <code>Gutenberg Library</code>. Stay tuned for more presentations.</p>
<h2 id="final-notes">Final Notes</h2>
<p>This event was driven in the framework of the <code>[Productivity Month] Boost your Productivity: Unlock your Potential</code> initiative, created by the Learning Lab team.</p>
<hr>
<p>
<img src="/blogPosts/gutenberg/20240328_gutenberg_atomichabits/gutenberg_logo.png" alt="Gutenberg" />

</p>
<hr>
]]></content:encoded></item><item><title>Node-Gyp and Python312</title><link>https://devblog.criticalmanufacturing.com/blog/20240205_break_compatibility_python/</link><pubDate>Mon, 05 Feb 2024 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20240205_break_compatibility_python/</guid><description>Python312 breaks node-gyp</description><content:encoded><![CDATA[<p>I want to take a look at a problem regarding node-gyp and Python3.1.2 and use that opportunity to talk a bit about both.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>The problem</li>
<li>What is node-gyp</li>
</ol>
<h2 id="the-problem">The Problem</h2>
<p>The other day, everything broke down&hellip; Depending on external systems makes us gain a lot of features, maturity and experience that was gathered by third parties, but you abdicate control. It&rsquo;s not uncommon, for small updates to generate disruption in our workflow. Being mindful of these trade-offs should be at the forefront of the decision making process of every developer, questions like stability, updates, community and documentation are key factors when deciding what dependencies to chose and build on.</p>
<p>Knowing how to read is also an important feature of a software engineer, mainly reading logs. We wasted some minutes not doing that and jumping to conclusions, but when we stopped to read the stack trace, we saw the following message:</p>
<p>
<img src="/blogPosts/IoT/20240205_break_compatibility_python/stacktrace_nodegyp.png" alt="Pipeline error" />

</p>
<p>This was a strange issue, as it was <strong>working on our local machines</strong>. So we already knew that the problem was either in the pipeline or in the dependencies installed in the system that ran the pipeline.</p>
<p>Looking carefully, we can see that the problem was on a dependency called <code>node-gyp</code>. The dependency finds the vs tools to build C++, but it subsequently fails when it reaches the <code>Python312</code> execution.</p>
<p>A quick search on the internet gave us some clues <a href="https://github.com/nodejs/node-gyp/issues/2869" target="_blank" rel="noopener"><code>distutils is removed in Python 3.12</code></a> and in the <a href="https://docs.python.org/3.10/library/distutils.html" target="_blank" rel="noopener">official documentation</a>. Looking further <a href="https://github.com/nodejs/node-gyp/issues/2966" target="_blank" rel="noopener"><code>[DOCS] The latest version of python (3.12) doesn't support node-gyp</code></a>, so we know that the node-gyp latest version supports <code>Python312</code>. Stupid question, what is the latest version and are we on it?</p>
<p>In the <code>node-gyp</code> github <a href="https://github.com/nodejs/node-gyp/releases" target="_blank" rel="noopener">releases</a> we know the latest version is <code>10.0.1</code>. Let&rsquo;s see what is ours by running in our package <code>npm list node-gyp</code>, we could also see globally by running <code>npm list node-gyp -g</code></p>
<p>
<img src="/blogPosts/IoT/20240205_break_compatibility_python/node-gyp_local_version.png" alt="Node-Gyp version" />

</p>
<p>We see that we have version 9.4.1 being used in the <a href="mailto:angular/cli@15.1.0">angular/cli@15.1.0</a>. In the github history we can see that the fix was applied to <a href="https://github.com/nodejs/gyp-next/pull/214" target="_blank" rel="noopener"><code>gyp-next</code></a> and this was released in <a href="https://github.com/nodejs/gyp-next/releases" target="_blank" rel="noopener"><code>gyp-next releases</code></a> v0.16.0, looking back at node-gyp we can see that the bump to gyp-next v0.16.0 only happens for <code>node-gyp</code> v10.x.</p>
<p>In order to fix this, we could update node-gyp but node-gyp is a sub-dependency of a product dependency, which may be impacted by a version change. The best approach is to follow the python <a href="https://docs.python.org/3.10/library/distutils.html" target="_blank" rel="noopener">official documentation</a> and install the setup tools.</p>
<p>Run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>python3 -m pip install setuptools
</span></span></code></pre></div><p>In order to check your python version:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>python3 --version
</span></span></code></pre></div><p>If your version is above 3.1.12 you will be impacted.</p>
<h2 id="what-is-node-gyp">What is node-gyp?</h2>
<p>Why does node which is a javascript engine, need python. Actually, the dependency that we&rsquo;ve been talking about <code>node-gyp</code> is a cross-platform command-line tool for compiling native addon modules for Node.js. This allows developers to write code in C, C++, or Rust and integrate them with JavaScript running in Node.js. <code>node-gyp</code> is a wrapper around <code>GYP</code> (Generate Your Projects), a meta-build system that generates project files for use with various IDEs and build systems, such as Visual Studio, Xcode, and make 12.</p>
<p>Notice, how this matches what we saw with our original problem, where the fix was not only in <code>node-gyp</code> but was in <code>gyp</code>. So actually, it&rsquo;s not <code>node-gyp</code> that needs python, but it&rsquo;s the parent dependency <a href="https://github.com/nodejs/gyp-next" target="_blank" rel="noopener"><code>gyp</code></a> that is built on python.</p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Scripting to Improve Work Flow</title><link>https://devblog.criticalmanufacturing.com/blog/20240102_scripting/</link><pubDate>Thu, 01 Feb 2024 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20240102_scripting/</guid><description>How to use scripting to improve your workflow</description><content:encoded><![CDATA[<p>We are going to talk a bit about how a small amount of scripting can improve your workflow. In this case our little script is for Windows Terminal.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview</li>
<li>Example</li>
<li>Conclusion</li>
</ol>
<h2 id="overview">Overview</h2>
<p>Scripting is how you fine tune your workflow to your needs. Maybe you are always running the same commands, or maybe it&rsquo;s a hassle to change between projects. In this example, I am going to use a real scenario that I had and how I addressed it with very little effort.</p>
<p>But I already have my own tools to optimize my flow and I love it how it is, then this is not for you. Keep up the great work!! The goal is just to expand what is possible in order for you to get more autonomous on improving your flow.</p>
<p>In this tutorial we will use powershell core and windows terminal, there are a million other tools, but these are what I am currently using in my work flow.</p>
<p><strong>Important</strong>: Scripting is a double edged knife. It is there to improve your work flow and to abstract complexity, but there&rsquo;s always a danger in abstracting complexity and losing control on the actions that you are doing. Also, be very mindful that you are using what exists and being more efficient, not trying to create a parallel solutions to problems everyone has. The goals is not create another &ldquo;magic solution&rdquo;.</p>
<h2 id="example">Example</h2>
<p>In my use case, I was tired of shifting between different local environments on my team. In my team, I have to boot the start the gui, the host, the message bus, a simulator application and an automation manager just to run the tests!!! 😩 Then, after all that I was always shifting between tabs on the console, because sometimes I was focusing on one component then on another. So I set about creating a workspace, that had to be predictable, easy to use and easy to switch between projects and I had very little time (no one gives you a sprint to improve your workflow, maybe they should 😜)</p>
<p>First I read the doc <a href="https://learn.microsoft.com/en-us/windows/terminal/command-line-arguments?tabs=windows" target="_blank" rel="noopener">Windows Terminal</a>. So, now I knew that splitting tabs and creating new tabs was super easy! Next step what do I actual care about, because that has to have a tab just for it. I work mostly with Connect IoT, so for sure the <code>Automation Manager</code> deserves a tab, then the host, the rest I don&rsquo;t care and doesn&rsquo;t give that great logging anyhow.</p>
<h1 id="powershell-profile">Powershell Profile</h1>
<p><a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.4" target="_blank" rel="noopener">Profiles</a> are a powerful tool to add things that you want to always exist in the context of your shell. Specific functions or variables that you always want accessible <code>The sky is the limit</code>.</p>
<p>Finding your profile, run a powershell console:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>$PROFILE | Select-Object *
</span></span></code></pre></div><p>But I don&rsquo;t have any:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (!(Test-Path -Path $PROFILE)) {
</span></span><span style="display:flex;"><span>  New-Item -ItemType File -Path $PROFILE -Force
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In the text editor of your choice, i.e notepad ++ open the profile and let&rsquo;s start cracking.</p>
<h1 id="customizing-the-profiler">Customizing the Profiler</h1>
<p>First thing I did was starting with one project (let&rsquo;s call it project X). I declared the paths that I thought were more or less constant as constants on the top of profile:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>$env:REPOS = <span style="color:#e6db74">&#34;C:\cmf\repos\DS\__PROJECT__&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$env:AM = <span style="color:#e6db74">&#34;C:\Users\jroque\Downloads\ActiveTestManager&#34;</span>
</span></span><span style="display:flex;"><span>$env:VF = <span style="color:#e6db74">&#34;C:\cmf\repos\DS\Library\src\VirtualFactory&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$env:BIZ_LOCAL = <span style="color:#e6db74">&#34;\LocalEnvironment\BusinessTier&#34;</span>
</span></span><span style="display:flex;"><span>$env:MB_LOCAL = <span style="color:#e6db74">&#34;\LocalEnvironment\MessageBusGateway&#34;</span>
</span></span><span style="display:flex;"><span>$env:HTML_LOCAL = <span style="color:#e6db74">&#34;\Features\Cmf.Custom.__PROJECT__\Cmf.Custom.__PROJECT__.HTML&#34;</span>
</span></span></code></pre></div><p>Then I set about creating a function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#66d9ef">Function</span> StartProject($Root, $Project, $DB) {
</span></span><span style="display:flex;"><span>	
</span></span><span style="display:flex;"><span>	wsl -- sudo service docker start
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">If</span> (docker ps --filter <span style="color:#e6db74">&#39;expose=1433&#39;</span> --format <span style="color:#e6db74">&#39;{{.ID}}&#39;</span>) { 
</span></span><span style="display:flex;"><span>     docker stop (docker ps --filter <span style="color:#e6db74">&#39;expose=1433&#39;</span> --format <span style="color:#e6db74">&#39;{{.ID}}&#39;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	docker start $DB 
</span></span><span style="display:flex;"><span>	Write-Host  $env:REPOS
</span></span><span style="display:flex;"><span>	Write-Host  $Root
</span></span><span style="display:flex;"><span>	Write-Host  $env:REPOS.replace(<span style="color:#e6db74">&#34;__PROJECT__&#34;</span>, $Root)
</span></span><span style="display:flex;"><span>	$Biz = Join-Path -Path $env:REPOS.replace(<span style="color:#e6db74">&#34;__PROJECT__&#34;</span>, $Root) -ChildPath $env:BIZ_LOCAL;
</span></span><span style="display:flex;"><span>	$MB = Join-Path -Path $env:REPOS.replace(<span style="color:#e6db74">&#34;__PROJECT__&#34;</span>, $Root) -ChildPath $env:MB_LOCAL;
</span></span><span style="display:flex;"><span>	$Html = Join-Path -Path $env:REPOS.replace(<span style="color:#e6db74">&#34;__PROJECT__&#34;</span>, $Root) -ChildPath $env:HTML_LOCAL.replace(<span style="color:#e6db74">&#34;__PROJECT__&#34;</span>, $Project);
</span></span><span style="display:flex;"><span>	$AM = Join-Path -Path $env:AM -ChildPath <span style="color:#e6db74">&#34;\scripts&#34;</span>;
</span></span><span style="display:flex;"><span>	
</span></span><span style="display:flex;"><span>	wt --maximized --title <span style="color:#e6db74">&#34;Local Env&#34;</span> -d $Biz pwsh -c .\Cmf.Foundation.Services.HostService.exe `; split-pane -d $Html pwsh -c ng serve `; split-pane -H -d $MB pwsh -c .\Cmf.MessageBus.Gateway.exe `; new-tab  --title <span style="color:#e6db74">&#34;VirtualFactory&#34;</span> -d $env:VF pwsh -c npm run start `; new-tab --title <span style="color:#e6db74">&#34;Automation Manager&#34;</span> -d $AM pwsh -c .\StartConsole.bat 
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>A function is something that if registered on the profile can then be executed after the shell is opened. My function has three parts, one just to find the paths of all the components I need to boot up and another to use the windows terminal CLI.</p>
<p>First I check if there is any docker container running that is occupying the default DB port. I am doing this as my components require the database to be up.</p>
<p>In the windows terminal CLI I am saying that I want launch a new terminal with the path to business and to execute the command <code>.\Cmf.Foundation.Services.HostService.exe</code>. Then I want to split the pane in the path of Html and run <code>ng serve</code>, the split the splitted panel horizontally in the path of the MessageBus and run <code>.\Cmf.MessageBus.Gateway.exe</code>. Then I want a new tab for a simulator called <code>Virtual Factory</code> and lastly another new tab for the automation manager.</p>
<p>I created a small function that the only thing that it does, is inform the user of what exists and asks for a choice. This function will orchestrate the project to start:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#66d9ef">Function</span> StartLocalEnv {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e"># Define menu options</span>
</span></span><span style="display:flex;"><span>	$options = @{
</span></span><span style="display:flex;"><span>		<span style="color:#e6db74">&#39;Active&#39;</span> = <span style="color:#e6db74">&#39;Active&#39;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#e6db74">&#39;Baseline&#39;</span> = <span style="color:#e6db74">&#39;Baseline&#39;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	Write-Host <span style="color:#e6db74">&#34;Select one of the supported Local Envs:&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e"># Display menu to the user</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">foreach</span> ($key <span style="color:#66d9ef">in</span> $options.Keys) {
</span></span><span style="display:flex;"><span>		Write-Host <span style="color:#e6db74">&#34;</span>$($options[$key])<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e"># Get user input</span>
</span></span><span style="display:flex;"><span>	$choice = Read-Host <span style="color:#e6db74">&#34;Select one of the options&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e"># Process user choice</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> ($options.ContainsKey($choice)) {
</span></span><span style="display:flex;"><span>		Write-Host <span style="color:#e6db74">&#34;You selected </span>$($options[$choice])<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">switch</span> ($choice) {
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#34;Active&#34;</span> {
</span></span><span style="display:flex;"><span>				StartProject <span style="color:#e6db74">&#34;Active&#34;</span> <span style="color:#e6db74">&#34;Active&#34;</span> <span style="color:#e6db74">&#34;ACTIVE_v1-DB&#34;</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#34;Baseline&#34;</span> {
</span></span><span style="display:flex;"><span>				StartProject <span style="color:#e6db74">&#34;Baseline&#34;</span> <span style="color:#e6db74">&#34;Baseline&#34;</span> <span style="color:#e6db74">&#34;Template_v2-DB&#34;</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">default</span> {
</span></span><span style="display:flex;"><span>				Write-Host <span style="color:#e6db74">&#34;Invalid choice. Please select a valid option.&#34;</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>		Write-Host <span style="color:#e6db74">&#34;Invalid choice. Please select a valid option.&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now let&rsquo;s see it work.</p>
<p>
<img src="/blogPosts/IoT/20240102_scripting/local_env.gif" alt="Seeing it Work" />

</p>
<p>Notice also that it is resilient, I can kill the console and start it again and it will keep the same context.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I know what you&rsquo;re thinking, but it&rsquo;s still missing so much. The goal here is to show the sandbox, now you need to tweak it and do what works best for you.</p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello-my-name-is-olivério-sousa-">Hello, my name is Olivério Sousa ✌️</h3>
<p>I am a TechLead at Deployment Services.</p>
<p>Skills: Tech Leading

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_oliverio_sousa.jpg"
    alt="Olivério Sousa"
    title="TechLead DS"
  />
  <figcaption style="text-align: center">TechLead DS</figcaption>
</figure>

</p>
<hr>
<p><br></br></p>
<h2 id="author-1">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Connect IoT Logging</title><link>https://devblog.criticalmanufacturing.com/blog/20231206_iot_logging/</link><pubDate>Wed, 06 Dec 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20231206_iot_logging/</guid><description>How to Use IoT Logs</description><content:encoded><![CDATA[<p>We are going to do a walkthrough of how the log works for Connect IoT, how you can read it and configure it.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview</li>
<li>Default Config</li>
<li>Reading the logs</li>
</ol>
<h2 id="overview">Overview</h2>
<p>Connect IoT is a back-end application, as such logging is one of the most crucial parts of understanding its execution. Logging is what brings visibility and transparency to what is happening in the application. It&rsquo;s common that iot, processes a lot of information and that you need to perform some investigation in order to spot what caused an error or an unpredicted behavior. In this blog post, we will walkthrough how the log is organized and how you can read it.</p>
<h2 id="default-config">Default Config</h2>
<p>When an Automation Manager is downloaded the system comes with a default strategy and structure for logging. The template for all <code>Automation Managers</code> can be found in a <code>Configuration</code> <code>/Cmf/System/Configuration/ConnectIoT/ConfigurationTemplate/</code> and can be changed for each <code>Automation Manager</code> in the <code>Configuration</code> button and editing the json.</p>
<p>Focusing in the subset for logging:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&#34;logging&#34;</span>: [
</span></span><span style="display:flex;"><span>		{
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;Console&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;options&#34;</span>: {
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;level&#34;</span>: <span style="color:#e6db74">&#34;debug&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;prettyPrint&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;colorizeMessage&#34;</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;applications&#34;</span>: [
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;*&#34;</span>
</span></span><span style="display:flex;"><span>			]
</span></span><span style="display:flex;"><span>		},
</span></span><span style="display:flex;"><span>		{
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;ControllersAndDrivers&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;File&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;options&#34;</span>: {
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;filename&#34;</span>: <span style="color:#e6db74">&#34;${applicationName}_${date}.log&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;dirname&#34;</span>: <span style="color:#e6db74">&#34;${temp}/ConnectIoT/Logs/Instances/${entityNameNormalized}/${componentId}&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;level&#34;</span>: <span style="color:#e6db74">&#34;debug&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;timestampFormat&#34;</span>: <span style="color:#e6db74">&#34;HH:mm:ss.SSSSS&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxSize&#34;</span>: <span style="color:#e6db74">&#34;10m&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxFiles&#34;</span>: <span style="color:#e6db74">&#34;30d&#34;</span>
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;applications&#34;</span>: [
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;AutomationController&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;Driver*&#34;</span>
</span></span><span style="display:flex;"><span>			]
</span></span><span style="display:flex;"><span>		},
</span></span><span style="display:flex;"><span>		{
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;ManagerAndMonitor&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;File&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;options&#34;</span>: {
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;filename&#34;</span>: <span style="color:#e6db74">&#34;${applicationName}_${date}.log&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;dirname&#34;</span>: <span style="color:#e6db74">&#34;${temp}/ConnectIoT/Logs/${applicationName}&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;level&#34;</span>: <span style="color:#e6db74">&#34;debug&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;timestampFormat&#34;</span>: <span style="color:#e6db74">&#34;HH:mm:ss.SSSSS&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxSize&#34;</span>: <span style="color:#e6db74">&#34;10m&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxFiles&#34;</span>: <span style="color:#e6db74">&#34;30d&#34;</span>
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;applications&#34;</span>: [
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;AutomationMonitor&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;AutomationManager&#34;</span>
</span></span><span style="display:flex;"><span>			]
</span></span><span style="display:flex;"><span>		},
</span></span><span style="display:flex;"><span>		{
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;Jobs&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;File&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;options&#34;</span>: {
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;filename&#34;</span>: <span style="color:#e6db74">&#34;Job_${jobId}_${date}.log&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;dirname&#34;</span>: <span style="color:#e6db74">&#34;${temp}/ConnectIoT/Jobs&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;auditFile&#34;</span>: <span style="color:#e6db74">&#34;${temp}/ConnectIoT/Jobs/.audit.json&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;level&#34;</span>: <span style="color:#e6db74">&#34;debug&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;timestampFormat&#34;</span>: <span style="color:#e6db74">&#34;HH:mm:ss.SSSSS&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxSize&#34;</span>: <span style="color:#e6db74">&#34;10m&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxFiles&#34;</span>: <span style="color:#e6db74">&#34;30d&#34;</span>
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;applications&#34;</span>: [
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;AutomationJob&#34;</span>
</span></span><span style="display:flex;"><span>			]
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>NOTE</strong>: All settings are explained in the documentation under <a href="https://help.criticalmanufacturing.com/installationguide/post-installation/connect-iot/connectiotautomationmanagerconfig/#logging-structure" target="_blank" rel="noopener">Help Logging Structure</a>
Right away we can see that we have multiple entries that are logging different applications. Let&rsquo;s go entry by entry.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&#34;logging&#34;</span>: [
</span></span><span style="display:flex;"><span>		{
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;Console&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;options&#34;</span>: {
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;level&#34;</span>: <span style="color:#e6db74">&#34;debug&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;prettyPrint&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;colorizeMessage&#34;</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;applications&#34;</span>: [
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;*&#34;</span>
</span></span><span style="display:flex;"><span>			]
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The first entry is of type <code>Console</code>, this type controls what is shown when you run the <code>Automation Manager</code> as a console (i.e running <code>StartConsole.bat</code> which is included in the .zip installation package). By default, applications is <code>*</code> this means it will log all the existing components of ConnectIoT. In <code>options</code>, it is set as level <code>debug</code> (possible levels are <code>debug</code>, <code>info</code>, <code>warn</code>, <code>error</code> in order of verbosity), also we have <code>prettyPrint</code> and <code>colorizeMessage</code>, these are just to format the message into a more reader friendly output.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&#34;logging&#34;</span>: [
</span></span><span style="display:flex;"><span>		{
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;ControllersAndDrivers&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;File&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;options&#34;</span>: {
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;filename&#34;</span>: <span style="color:#e6db74">&#34;${applicationName}_${date}.log&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;dirname&#34;</span>: <span style="color:#e6db74">&#34;${temp}/ConnectIoT/Logs/Instances/${entityNameNormalized}/${componentId}&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;level&#34;</span>: <span style="color:#e6db74">&#34;debug&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;timestampFormat&#34;</span>: <span style="color:#e6db74">&#34;HH:mm:ss.SSSSS&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxSize&#34;</span>: <span style="color:#e6db74">&#34;10m&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxFiles&#34;</span>: <span style="color:#e6db74">&#34;30d&#34;</span>
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;applications&#34;</span>: [
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;AutomationController&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;Driver*&#34;</span>
</span></span><span style="display:flex;"><span>			]
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We can see the second entry has more things going on. This log applies to type <code>File</code>, so we know this log will be persisted in a file. Now the applications are not set to everything, but this entry in the log configuration only applies to the <code>Automation Controller</code> and to all <code>Driver*</code> (notice the <code>*</code>, to signal all applications that start with <code>Driver</code>).</p>
<p>In the <code>options</code>, we now have some more interesting arguments, the <code>dirname</code> and <code>filename</code> will control the folder and file structure of the logs.</p>
<p>The <code>timestampFormat</code> is a value appended to the beginning of all log entries.</p>
<p>The <code>maxSize</code> and <code>maxFiles</code> are probably the most important configuration for the log. The log out of the box comes with a roll-over mechanism, in this case we can read the configuration as saying that every time a file reaches 10mb, it will generate a new file (i.e <code>AutomationController_2023-12-06.log</code> -&gt; <code>AutomationController_2023-12-06.log1</code>), if a log is older than 30d it will be deleted as it is considered as stale. The <code>maxFiles</code> can define a time limit or a set number of files.</p>
<p><strong>Note</strong>: Notice that we allow not only static text, but also some tokens that are replaced in runtime all tokens are documented in the help. The Normalized, can be added to perform some operations such as encoding and replacing special characters for <code>_</code>.</p>
<p><strong>Important</strong>: The logs occupy disk space, if you are dealing with an integration that is very verbose and generates a lot of logs, keep in mind that the disk space will be proportional to what you are logging. Feel free to tweak the settings as needed.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&#34;logging&#34;</span>: [
</span></span><span style="display:flex;"><span>		{
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;id&#34;</span>: <span style="color:#e6db74">&#34;ManagerAndMonitor&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;File&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;options&#34;</span>: {
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;filename&#34;</span>: <span style="color:#e6db74">&#34;${applicationName}_${date}.log&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;dirname&#34;</span>: <span style="color:#e6db74">&#34;${temp}/ConnectIoT/Logs/${applicationName}&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;level&#34;</span>: <span style="color:#e6db74">&#34;debug&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;timestampFormat&#34;</span>: <span style="color:#e6db74">&#34;HH:mm:ss.SSSSS&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxSize&#34;</span>: <span style="color:#e6db74">&#34;10m&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">&#34;maxFiles&#34;</span>: <span style="color:#e6db74">&#34;30d&#34;</span>
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&#34;applications&#34;</span>: [
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;AutomationMonitor&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#e6db74">&#34;AutomationManager&#34;</span>
</span></span><span style="display:flex;"><span>			]
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The third entry is very similar to the second entry. Now we change the dirname, as the manager and monitor are not grouped by instances and we change the <code>applications</code> to monitor the <code>Automation Monitor</code> and the <code>Automation Manager</code>.</p>
<p>When generating the logs you will see a structure similar to this:</p>
<ul>
<li>ConnectIoT
<ul>
<li>Logs
<ul>
<li>Automation Manager
<ul>
<li>(i.e AutomationManager_2023-12-06.log)</li>
</ul>
</li>
<li>Automation Monitor
<ul>
<li>(i.e AutomationMonitor_2023-12-06.log)</li>
</ul>
</li>
<li>Instances
<ul>
<li><code>&lt;Entity Name&gt;</code>
<ul>
<li>Controller
<ul>
<li>(i.e AutomationController_2023-12-06.log)</li>
</ul>
</li>
<li><code>&lt;Driver FriendlyName&gt;</code>
<ul>
<li>(i.e DriverOpcUA_2023-12-06.log)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="reading-the-logs">Reading the logs</h2>
<h3 id="automation-manager">Automation Manager</h3>
<p>Let&rsquo;s take a look at the beginning. This is a small snippet of the first log of the <code>Automation Manager</code>:</p>
<pre tabindex="0"><code class="language-log" data-lang="log">11:23:11.49149 info: Starting Manager with pid &#34;31468&#34;.
11:23:11.49349 debug: Command line: _=[], config=&#39;..\config.json&#39;, monitorPort=0, monitor-port=0, monitorHost=&#39;&#39;, monitor-host=&#39;&#39;, monitorApplication=&#39;&#39;, monitor-application=&#39;&#39;, $0=&#39;..\src\index.js&#39;
11:23:11.51952 debug: Monitor applications persistency data update with data from the following path &#34;C:\Users\jroque\Downloads\ActiveTestManager\monitorApplication.json&#34;
11:23:11.51952 debug: Using file &#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39; for configurations
11:23:11.51952 debug: Registering configuration section &#39;logging&#39; (Logging section)
11:23:11.51952 info: Parsing and validating configuration file &#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;
11:23:11.52052 debug: Using the following configuration settings:
Logging:
  Console (Console):
    level=&#39;debug&#39;, specificLevelLock=&#39;false&#39;, label=&#39;AutomationManager, format=&#39;${log.timestamp} ${log.level}: ${log.message}&#39;
    timestamp=&#39;true&#39;, timestampFormat=&#39;YYYY-MM-DD HH:mm:ss.SSS&#39;, maxLength=&#39;0&#39;
    colorize=&#39;true&#39;, colorizeMessage=&#39;true&#39;
    applications=&#39;&lt;All&gt;&#39;
  ManagerAndMonitor (File):
    level=&#39;debug&#39;, specificLevelLock=&#39;false&#39;, label=&#39;AutomationManager, format=&#39;${log.timestamp} ${log.level}: ${log.message}&#39;
    timestamp=&#39;true&#39;, timestampFormat=&#39;HH:mm:ss.SSSSS&#39;, maxLength=&#39;0&#39;
    dirname=&#39;C:/Users/jroque/Downloads/ActiveTestManager/ConnectIoT/Logs/AutomationManager&#39;, filename=&#39;AutomationManager_%DATE%.log&#39;, options=&#39;mode=511, flags=&#39;a&#39;&#39;
    datePattern=&#39;YYYY-MM-DD&#39;, maxSize=&#39;10m&#39;, maxFiles=&#39;30d&#39;, zippedArchive=&#39;false&#39;
    applications=&#39;[AutomationMonitor, AutomationManager]&#39;
11:23:11.52252 debug: This application is not allowed to use this transport (ControllersAndDrivers)
11:23:11.54254 debug: This application is not allowed to use this transport (Jobs)
11:23:11.54254 warn: *** Minimum log entries set to &#39;debug&#39; ***
11:23:11.54254 info:   Id=&#39;ActiveTestManager&#39;.
11:23:11.54254 info:   Configuration file=&#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;.
11:23:11.54254 info:   Monitor Address=&#39;localhost:0&#39;.
11:23:11.54354 info:   Monitor Application=&#39;C:/Users/jroque/Downloads/ActiveTestManager/ConnectIoT/Cache/connect-iot-monitor@10.1.2-202309224/src/index.js&#39;.
11:23:11.54655 debug: Starting Monitor &#39;C:/Users/jroque/Downloads/ActiveTestManager/ConnectIoT/Cache/connect-iot-monitor@10.1.2-202309224/src/index.js&#39; with args: &#39;[--config=C:\Users\jroque\Downloads\ActiveTestManager\config.json, --id=ActiveTestManager, --dev=false]&#39;
11:23:11.55455 info: Monitor started with pid 29084.
11:23:11.55455 info: Manager process started with success
</code></pre><p>As you can see the logs always start with timestamp the verbosity and then the log message. Right away we can see that the <code>Automation Manager</code> was started with pid <code>31468</code> and all the arguments used for starting the Manager. It is also explicit that the Monitor was started. These are the kinds of logs that you can expect in the Automation Manager as its main function is starting and stopping processes. The <code>Automation Monitor</code> and the <code>Automation Manager</code> logs are very useful to validate configurations and check if the boot up sequence is correct.</p>
<h3 id="automation-monitor">Automation Monitor</h3>
<p>Looking at the Monitor. Let&rsquo;s split the logs in sequential steps, so we can understand what is happening. First off the booting up sequence:</p>
<pre tabindex="0"><code class="language-log" data-lang="log">11:23:13.40440 debug: Using file &#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39; for configurations
11:23:13.40541 debug: Registering configuration section &#39;logging&#39; (Logging section)
11:23:13.40541 debug: Registering configuration section &#39;monitor&#39; (Monitor Section)
11:23:13.40541 debug: Registering configuration section &#39;monitor/processCommunication&#39; (Process communication for Monitor)
11:23:13.40541 debug: Registering configuration section &#39;repository&#39; (Repository section)
11:23:13.40541 debug: Registering configuration section &#39;system&#39; (System access section)
11:23:13.40641 info: Parsing and validating configuration file &#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;
11:23:13.40741 debug: Using the following configuration settings:
Logging:
  Console (Console):
    level=&#39;debug&#39;, specificLevelLock=&#39;false&#39;, label=&#39;AutomationMonitor, format=&#39;${log.timestamp} ${log.level}: ${log.message}&#39;
    timestamp=&#39;true&#39;, timestampFormat=&#39;YYYY-MM-DD HH:mm:ss.SSS&#39;, maxLength=&#39;0&#39;
    colorize=&#39;true&#39;, colorizeMessage=&#39;true&#39;
    applications=&#39;&lt;All&gt;&#39;
  ManagerAndMonitor (File):
    level=&#39;debug&#39;, specificLevelLock=&#39;false&#39;, label=&#39;AutomationMonitor, format=&#39;${log.timestamp} ${log.level}: ${log.message}&#39;
    timestamp=&#39;true&#39;, timestampFormat=&#39;HH:mm:ss.SSSSS&#39;, maxLength=&#39;0&#39;
    dirname=&#39;C:/Users/jroque/Downloads/ActiveTestManager/ConnectIoT/Logs/AutomationMonitor&#39;, filename=&#39;AutomationMonitor_%DATE%.log&#39;, options=&#39;mode=511, flags=&#39;a&#39;&#39;
    datePattern=&#39;YYYY-MM-DD&#39;, maxSize=&#39;10m&#39;, maxFiles=&#39;30d&#39;, zippedArchive=&#39;false&#39;
    applications=&#39;[AutomationMonitor, AutomationManager]&#39;
Monitor:
  notifyBeforeKill=&#39;true&#39;, killNotificationTimeout=&#39;10000&#39; ms
  retryAttempts=&#39;30&#39;, sleepBetweenAttempts=&#39;1000&#39; ms
  processHeartbeat: isEnabled=&#39;true&#39;
   interval=&#39;60000&#39;, timeout=&#39;10000&#39;
   allowedFailures=&#39;3&#39;, action=&#39;Restart&#39;
  processCommunication: useSsl=&#39;false&#39;
Repository: type=&#39;System&#39;
System:
  tenantName=&#39;BBRAUN&#39;, loadBalancing=&#39;false&#39;, urlSuffix=&#39;undefined&#39;
  address=&#39;localhost&#39;, port=&#39;903&#39;, ssl=&#39;false&#39;, timeout=&#39;120000 ms&#39;
  authentication: type=&#39;Password&#39;
    domain=&#39;cmf&#39;, user=&#39;jroque&#39;
11:23:13.40941 debug: This application is not allowed to use this transport (ControllersAndDrivers)
11:23:13.42743 debug: This application is not allowed to use this transport (Jobs)
11:23:13.42743 warn: *** Minimum log entries set to &#39;debug&#39; ***
11:23:13.42843 info: Starting Monitor with pid &#34;29084&#34;.
11:23:13.42843 debug: Command line: _=[], config=&#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;, id=&#39;ActiveTestManager&#39;, dev=&#39;false&#39;, monitorPort=0, mp=0, monitor-port=0, monitorHost=&#39;localhost&#39;, mh=&#39;localhost&#39;, monitor-host=&#39;localhost&#39;, cache=&#39;&#39;, cd=&#39;&#39;, $0=&#39;..\ConnectIoT\Cache\connect-iot-monitor@10.1.2-202309224\src\index.js&#39;
11:23:13.42943 info:   Id=&#39;ActiveTestManager&#39;
11:23:13.42943 info:   ConfigurationFile=&#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;
11:23:13.42943 info:   HostName=&#39;localhost&#39;
11:23:13.42943 info:   MonitorHost=&#39;localhost&#39;
11:23:13.42943 info:   MonitorPort=&#39;0&#39;
11:23:13.42943 info:   CacheLocation=&#39;C:/Users/jroque/Downloads/ActiveTestManager/ConnectIoT/Cache&#39;
11:23:13.43443 info: Monitor version &#39;10.1.2-202309224&#39; is starting...
11:23:13.43443 debug: Setting up MES system connection
11:23:13.43443 info: Setting up SystemAPI Rest communication
11:23:13.43443 debug: tenantName=&#39;BBRAUN&#39;, loadBalancing=&#39;false&#39;, address=&#39;localhost&#39;, port=&#39;903&#39;, ssl=&#39;false&#39;
11:23:13.43443 info: Authenticating client...
11:23:13.43443 debug: Password Authenticate: domain=&#39;cmf&#39;, username=&#39;jroque&#39;
11:23:13.52052 info: Authentication successful for user &#39;CMF\jroque&#39;
11:23:13.52553 debug: Retrieving data identified with &#39;httpPort&#39;
11:23:13.52753 info: Monitor is starting on &#39;localhost:0&#39;
11:23:13.52853 info: Listening websocket http address &#34;:::64211&#34; for driver and controller messages
11:23:13.52953 debug: Storing data for &#39;httpPort&#39;
11:23:13.53053 debug: Identifying Automation Manager &#39;ActiveTestManager&#39; in System
11:23:13.53053 debug: Executing query &#39;IdentifyAutomationManager&#39; in System, over entity &#39;AutomationManager&#39;
11:23:13.53554 info: Connected with message bus!
</code></pre><p>Notice the node process is started <code>info: Starting Monitor with pid &quot;29084&quot;</code>. Then the configurations for the monitor. All boot up phases of the components are similar. It&rsquo;s also clear that the monitor communicates with the MES (System) and with the MessageBus.</p>
<p>The next phase, it retrieves the information from the MES regarding what processes it needs to start and request to the manager to start those processes (in this case we have a controller with two drivers).</p>
<pre tabindex="0"><code class="language-log" data-lang="log">11:23:13.56156 info: Executed query, and received &#39;1&#39; results
11:23:13.56156 debug: Identified Automation Manager Id=&#39;2310311623180000002&#39;, Name=&#39;ActiveTestManager&#39;, MonitorPackageVersion=&#39;10.1.2-202309224&#39;
11:23:13.56356 info: Getting list of instances (controller and driver) to monitor (&#39;ActiveTestManager&#39;)
11:23:13.56356 debug: Executing query &#39;ConnectIoTGetInstancesToRun&#39; in System, over entity &#39;AutomationDriverInstance&#39;
11:23:13.56356 info: Starting process heartbeat
11:23:13.56456 info: Monitor process started with success
11:23:13.73974 info: Executed query, and received &#39;3&#39; results
11:23:13.74074 info: Resolving EntityType name from the Id &#39;1805111613350000045&#39;
11:23:13.88789 info: Resolved EntityType name &#39;Resource&#39; from the Id &#39;1805111613350000045&#39;
11:23:13.88789 debug: Getting ObjectById: Id=&#39;2310311623180000157&#39;, Type=&#39;Resource&#39;
11:23:14.17217 info: Resolved Object Name=&#39;ControllerTest_FleetManager_CleanRoom&#39; from Id=&#39;2310311623180000157&#39;
11:23:14.17217 debug: Getting ObjectById: Id=&#39;2310311623180000158&#39;, Type=&#39;Resource&#39;
11:23:14.18118 info: Resolved Object Name=&#39;FactoryAutomationWorkerController&#39; from Id=&#39;2310311623180000158&#39;
11:23:14.18118 info: Preparing package &#39;@criticalmanufacturing/connect-iot-driver-opcua&#39;, version &#39;10.1.2-202309224&#39;
11:23:14.18218 info: Resolving package &#39;@criticalmanufacturing/connect-iot-driver-opcua&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:14.20020 info: Found package &#39;@criticalmanufacturing/connect-iot-driver-opcua&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-opcua@10.1.2-202309224&#39;
11:23:14.20020 info: Preparing package &#39;@criticalmanufacturing/connect-iot-controller&#39;, version &#39;10.1.2-202309224&#39;
11:23:14.20120 info: Resolving package &#39;@criticalmanufacturing/connect-iot-controller&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:14.20821 info: Found package &#39;@criticalmanufacturing/connect-iot-controller&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller@10.1.2-202309224&#39;
11:23:14.20821 info: Preparing package &#39;@criticalmanufacturing/connect-iot-driver-opcua&#39;, version &#39;10.1.2-202309224&#39;
11:23:14.20821 info: Resolving package &#39;@criticalmanufacturing/connect-iot-driver-opcua&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:14.20921 info: Found package &#39;@criticalmanufacturing/connect-iot-driver-opcua&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-opcua@10.1.2-202309224&#39;
11:23:14.20921 info: Preparing package &#39;@criticalmanufacturing/connect-iot-driver-factoryautomation&#39;, version &#39;10.1.2-202309224&#39;
11:23:14.20921 info: Resolving package &#39;@criticalmanufacturing/connect-iot-driver-factoryautomation&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:14.21622 info: Found package &#39;@criticalmanufacturing/connect-iot-driver-factoryautomation&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-factoryautomation@10.1.2-202309224&#39;
11:23:14.21622 info: Preparing package &#39;@criticalmanufacturing/connect-iot-controller&#39;, version &#39;10.1.2-202309224&#39;
11:23:14.21622 info: Resolving package &#39;@criticalmanufacturing/connect-iot-controller&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:14.21722 info: Found package &#39;@criticalmanufacturing/connect-iot-controller&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller@10.1.2-202309224&#39;
11:23:14.21822 info: Processes to start:
11:23:14.21822 info: AutomationControllerInstance/2310311623180000011  @criticalmanufacturing/connect-iot-controller@10.1.2-202309224
11:23:14.21822 info:     AutomationDriverInstance/2310311623180000032  @criticalmanufacturing/connect-iot-driver-opcua@10.1.2-202309224 (Server)
11:23:14.21922 info:     AutomationDriverInstance/2310311623180000033  @criticalmanufacturing/connect-iot-driver-opcua@10.1.2-202309224 (Client)
11:23:14.21922 info: AutomationControllerInstance/2310311623180000012  @criticalmanufacturing/connect-iot-controller@10.1.2-202309224
11:23:14.21922 info:     AutomationDriverInstance/2310311623180000025  @criticalmanufacturing/connect-iot-driver-factoryautomation@10.1.2-202309224 (Handler)
11:23:14.21922 info: Starting process id=&#39;AutomationDriverInstance/2310311623180000032&#39;, name=&#39;@criticalmanufacturing/connect-iot-driver-opcua&#39;, version=&#39;10.1.2-202309224&#39;
11:23:14.21922 info: Starting &#39;Server&#39; main script &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-opcua@10.1.2-202309224\src\index.js&#39;
11:23:14.22022 info: Requesting fork Id=&#39;AutomationDriverInstance/2310311623180000032&#39;, Path=&#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-opcua@10.1.2-202309224\src\index.js&#39;
11:23:14.22022 debug:  Arguments=--id=AutomationDriverInstance/2310311623180000032 --managerId=ActiveTestManager --componentId=Server --serverHost=localhost --monitorPort=64211 --monitorHost=localhost --monitorToken=monitorSecurityToken --monitorSsl=false --config=C:\Users\jroque\Downloads\ActiveTestManager\config.json --dev=false --entityName=ControllerTest_FleetManager_CleanRoom
11:23:14.22723 info: Received notification of process &#39;AutomationDriverInstance/2310311623180000032&#39; start with Pid &#39;11724&#34;
11:23:14.22723 info: Started process &#39;AutomationDriverInstance/2310311623180000032&#39; not being handled by Monitor Pid=&#39;11724&#39;. Message ignored?
11:23:14.22723 info: fork &#34;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-opcua@10.1.2-202309224\src\index.js&#34; succeeded with pid &#34;11724&#34;
11:23:14.22823 info: Starting process id=&#39;AutomationControllerInstance/2310311623180000011&#39;, name=&#39;@criticalmanufacturing/connect-iot-controller&#39;, version=&#39;10.1.2-202309224&#39;
11:23:14.22823 info: Starting &#39;Controller&#39; main script &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller@10.1.2-202309224\src\index.js&#39;
11:23:14.22823 info: Requesting fork Id=&#39;AutomationControllerInstance/2310311623180000011&#39;, Path=&#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller@10.1.2-202309224\src\index.js&#39;
11:23:14.22823 debug:  Arguments=--id=AutomationControllerInstance/2310311623180000011 --managerId=ActiveTestManager --componentId=Controller --serverHost=localhost --monitorPort=64211 --monitorHost=localhost --monitorToken=monitorSecurityToken --monitorSsl=false --config=C:\Users\jroque\Downloads\ActiveTestManager\config.json --dev=false --entityName=ControllerTest_FleetManager_CleanRoom
11:23:14.23323 info: Received notification of process &#39;AutomationControllerInstance/2310311623180000011&#39; start with Pid &#39;30428&#34;
11:23:14.23323 info: Started process &#39;AutomationControllerInstance/2310311623180000011&#39; not being handled by Monitor Pid=&#39;30428&#39;. Message ignored?
11:23:14.23423 info: fork &#34;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller@10.1.2-202309224\src\index.js&#34; succeeded with pid &#34;30428&#34;
11:23:14.23423 info: Starting process id=&#39;AutomationDriverInstance/2310311623180000033&#39;, name=&#39;@criticalmanufacturing/connect-iot-driver-opcua&#39;, version=&#39;10.1.2-202309224&#39;
11:23:14.23423 info: Starting &#39;Client&#39; main script &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-opcua@10.1.2-202309224\src\index.js&#39;
11:23:14.23423 info: Requesting fork Id=&#39;AutomationDriverInstance/2310311623180000033&#39;, Path=&#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-opcua@10.1.2-202309224\src\index.js&#39;
11:23:14.23423 debug:  Arguments=--id=AutomationDriverInstance/2310311623180000033 --managerId=ActiveTestManager --componentId=Client --serverHost=localhost --monitorPort=64211 --monitorHost=localhost --monitorToken=monitorSecurityToken --monitorSsl=false --config=C:\Users\jroque\Downloads\ActiveTestManager\config.json --dev=false --entityName=ControllerTest_FleetManager_CleanRoom
11:23:14.24224 info: Received notification of process &#39;AutomationDriverInstance/2310311623180000033&#39; start with Pid &#39;19324&#34;
11:23:14.24224 info: Started process &#39;AutomationDriverInstance/2310311623180000033&#39; not being handled by Monitor Pid=&#39;19324&#39;. Message ignored?
11:23:14.24224 info: fork &#34;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-opcua@10.1.2-202309224\src\index.js&#34; succeeded with pid &#34;19324&#34;
11:23:14.24324 info: Starting process id=&#39;AutomationDriverInstance/2310311623180000025&#39;, name=&#39;@criticalmanufacturing/connect-iot-driver-factoryautomation&#39;, version=&#39;10.1.2-202309224&#39;
11:23:14.24324 info: Starting &#39;Handler&#39; main script &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-factoryautomation@10.1.2-202309224\src\index.js&#39;
11:23:14.24324 info: Requesting fork Id=&#39;AutomationDriverInstance/2310311623180000025&#39;, Path=&#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-factoryautomation@10.1.2-202309224\src\index.js&#39;
11:23:14.24324 debug:  Arguments=--id=AutomationDriverInstance/2310311623180000025 --managerId=ActiveTestManager --componentId=Handler --serverHost=localhost --monitorPort=64211 --monitorHost=localhost --monitorToken=monitorSecurityToken --monitorSsl=false --config=C:\Users\jroque\Downloads\ActiveTestManager\config.json --dev=false --entityName=FactoryAutomationWorkerController
11:23:14.24925 info: Received notification of process &#39;AutomationDriverInstance/2310311623180000025&#39; start with Pid &#39;22304&#34;
11:23:14.24925 info: Started process &#39;AutomationDriverInstance/2310311623180000025&#39; not being handled by Monitor Pid=&#39;22304&#39;. Message ignored?
11:23:14.25025 info: fork &#34;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-driver-factoryautomation@10.1.2-202309224\src\index.js&#34; succeeded with pid &#34;22304&#34;
11:23:14.25025 info: Starting process id=&#39;AutomationControllerInstance/2310311623180000012&#39;, name=&#39;@criticalmanufacturing/connect-iot-controller&#39;, version=&#39;10.1.2-202309224&#39;
11:23:14.25025 info: Starting &#39;Controller&#39; main script &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller@10.1.2-202309224\src\index.js&#39;
11:23:14.25025 info: Requesting fork Id=&#39;AutomationControllerInstance/2310311623180000012&#39;, Path=&#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller@10.1.2-202309224\src\index.js&#39;
11:23:14.25025 debug:  Arguments=--id=AutomationControllerInstance/2310311623180000012 --managerId=ActiveTestManager --componentId=Controller --serverHost=localhost --monitorPort=64211 --monitorHost=localhost --monitorToken=monitorSecurityToken --monitorSsl=false --config=C:\Users\jroque\Downloads\ActiveTestManager\config.json --dev=false --entityName=FactoryAutomationWorkerController
11:23:14.25726 info: Received notification of process &#39;AutomationControllerInstance/2310311623180000012&#39; start with Pid &#39;4144&#34;
11:23:14.25726 info: Started process &#39;AutomationControllerInstance/2310311623180000012&#39; not being handled by Monitor Pid=&#39;4144&#39;. Message ignored?
11:23:14.25726 info: fork &#34;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller@10.1.2-202309224\src\index.js&#34; succeeded with pid &#34;4144&#34;
11:23:14.25826 info: Processes and packages processed
11:23:15.47447 debug: Client Connected! from 64223. Have now 1 clients.
11:23:15.48348 debug: Client Connected! from 64226. Have now 2 clients.
11:23:16.59459 debug: Client Connected! from 64232. Have now 3 clients.
11:23:16.60260 debug: Client Connected! from 64233. Have now 4 clients.
11:23:16.62462 info: Component &#34;AutomationDriverInstance/2310311623180000032&#34; notified it just become available
11:23:16.62963 info: Notifying &#39;AutomationControllerInstance/2310311623180000011&#39; with connection information to &#39;AutomationDriverInstance/2310311623180000032&#39; (Server)
11:23:16.62963 debug: Data provided: type=&#39;websocket&#39;, host=&#39;localhost&#39;, port=&#39;64235&#39;, ssl=&#39;false&#39;
11:23:16.63163 info: Component &#34;AutomationDriverInstance/2310311623180000033&#34; notified it just become available
11:23:16.63263 info: Notifying &#39;AutomationControllerInstance/2310311623180000011&#39; with connection information to &#39;AutomationDriverInstance/2310311623180000033&#39; (Client)
11:23:16.63363 debug: Data provided: type=&#39;websocket&#39;, host=&#39;localhost&#39;, port=&#39;64234&#39;, ssl=&#39;false&#39;
11:23:16.80080 info: Resolving package &#39;@criticalmanufacturing/connect-iot-controller-engine-core-tasks&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:16.80180 info: Resolving package &#39;@criticalmanufacturing/connect-iot-controller-engine-factoryautomation-tasks&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:16.80881 info: Found package &#39;@criticalmanufacturing/connect-iot-controller-engine-core-tasks&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller-engine-core-tasks@10.1.2-202309224&#39;
11:23:16.81782 info: Found package &#39;@criticalmanufacturing/connect-iot-controller-engine-factoryautomation-tasks&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller-engine-factoryautomation-tasks@10.1.2-202309224&#39;
11:23:17.01001 debug: Client Connected! from 64236. Have now 5 clients.
11:23:17.08509 info: Component &#34;AutomationDriverInstance/2310311623180000025&#34; notified it just become available
11:23:17.08609 info: Notifying &#39;AutomationControllerInstance/2310311623180000012&#39; with connection information to &#39;AutomationDriverInstance/2310311623180000025&#39; (Handler)
11:23:17.08609 debug: Data provided: type=&#39;websocket&#39;, host=&#39;localhost&#39;, port=&#39;64237&#39;, ssl=&#39;false&#39;
11:23:17.55355 info: Resolving package &#39;@criticalmanufacturing/connect-iot-controller-engine-core-tasks&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:17.55455 info: Found package &#39;@criticalmanufacturing/connect-iot-controller-engine-core-tasks&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller-engine-core-tasks@10.1.2-202309224&#39;
11:23:17.55656 info: Resolving package &#39;@criticalmanufacturing/connect-iot-controller-engine-opcua-tasks&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:17.56657 info: Found package &#39;@criticalmanufacturing/connect-iot-controller-engine-opcua-tasks&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller-engine-opcua-tasks@10.1.2-202309224&#39;
11:23:18.42442 info: Component &#34;AutomationControllerInstance/2310311623180000012&#34; notified it just become available
11:23:18.42543 info: Notifying &#39;AutomationControllerInstance/2310311623180000012&#39; with connection information to &#39;AutomationDriverInstance/2310311623180000025&#39; (Handler)
11:23:18.42543 debug: Data provided: type=&#39;websocket&#39;, host=&#39;localhost&#39;, port=&#39;64237&#39;, ssl=&#39;false&#39;
11:23:18.55055 info: Received order to &#39;setautomationstates&#39; the Controller &#39;AutomationControllerInstance/2310311623180000012&#39; for this AutomationManager
11:23:18.82482 info: Resolving package &#39;@criticalmanufacturing/connect-iot-controller-engine-factoryautomation-tasks&#39; with version &#39;10.1.2-202309224&#39; location.
11:23:18.82583 info: Found package &#39;@criticalmanufacturing/connect-iot-controller-engine-factoryautomation-tasks&#39; with version &#39;10.1.2-202309224&#39; in &#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller-engine-factoryautomation-tasks@10.1.2-202309224&#39;
11:23:19.18519 info: Component &#34;AutomationControllerInstance/2310311623180000011&#34; notified it just become available
</code></pre><p>The processes to start are found in:</p>
<pre tabindex="0"><code class="language-log" data-lang="log">11:23:14.21822 info: Processes to start:
11:23:14.21822 info: AutomationControllerInstance/2310311623180000011  @criticalmanufacturing/connect-iot-controller@10.1.2-202309224
11:23:14.21822 info:     AutomationDriverInstance/2310311623180000032  @criticalmanufacturing/connect-iot-driver-opcua@10.1.2-202309224 (Server)
11:23:14.21922 info:     AutomationDriverInstance/2310311623180000033  @criticalmanufacturing/connect-iot-driver-opcua@10.1.2-202309224 (Client)
11:23:14.21922 info: AutomationControllerInstance/2310311623180000012  @criticalmanufacturing/connect-iot-controller@10.1.2-202309224
11:23:14.21922 info:     AutomationDriverInstance/2310311623180000025  @criticalmanufacturing/connect-iot-driver-factoryautomation@10.1.2-202309224 (Handler)
</code></pre><p>Then the retrieval of all the cached packages, like for example <code>info: Resolving package '@criticalmanufacturing/connect-iot-controller-engine-core-tasks' with version '10.1.2-202309224' location</code>. As the components are started by the manager the monitor will log that they are now available (i.e <code>11:23:18.42442 info: Component &quot;AutomationControllerInstance/2310311623180000012&quot; notified it just become available</code>).</p>
<h3 id="automation-controller">Automation Controller</h3>
<p>The <code>Automation Controller</code> logs all the logic in our workflows execution will be described. Everything that is orchestrated inside the workflows will be logged in this component.</p>
<pre tabindex="0"><code class="language-log" data-lang="log">11:23:15.34034 debug: Using file &#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39; for configurations
11:23:15.34334 debug: Registering configuration section &#39;logging&#39; (Logging section)
11:23:15.34334 debug: Registering configuration section &#39;controller&#39; (Controller Section)
11:23:15.34334 debug: Registering configuration section &#39;monitor/processCommunication&#39; (Process communication with Monitor)
11:23:15.34334 debug: Registering configuration section &#39;driver/processCommunication&#39; (Process communication with Drivers)
11:23:15.34334 debug: Registering configuration section &#39;system&#39; (System access section)
11:23:15.34334 info: Parsing and validating configuration file &#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;
11:23:15.34635 debug: Using the following configuration settings:
Logging:
  Console (Console):
    level=&#39;debug&#39;, specificLevelLock=&#39;false&#39;, label=&#39;AutomationController, format=&#39;${log.timestamp} ${log.level}: ${log.message}&#39;
    timestamp=&#39;true&#39;, timestampFormat=&#39;YYYY-MM-DD HH:mm:ss.SSS&#39;, maxLength=&#39;0&#39;
    colorize=&#39;true&#39;, colorizeMessage=&#39;true&#39;
    applications=&#39;&lt;All&gt;&#39;
  ControllersAndDrivers (File):
    level=&#39;debug&#39;, specificLevelLock=&#39;false&#39;, label=&#39;AutomationController, format=&#39;${log.timestamp} ${log.level}: ${log.message}&#39;
    timestamp=&#39;true&#39;, timestampFormat=&#39;HH:mm:ss.SSSSS&#39;, maxLength=&#39;0&#39;
    dirname=&#39;C:/Users/jroque/Downloads/ActiveTestManager/ConnectIoT/Logs/Instances/ControllerTest_FleetManager_CleanRoom/Controller&#39;, filename=&#39;AutomationController_%DATE%.log&#39;, options=&#39;mode=511, flags=&#39;a&#39;&#39;
    datePattern=&#39;YYYY-MM-DD&#39;, maxSize=&#39;10m&#39;, maxFiles=&#39;30d&#39;, zippedArchive=&#39;false&#39;
    applications=&#39;[AutomationController, Driver*]&#39;
Controller:
  retryAttempts=&#39;30&#39;, sleepBetweenAttempts=&#39;1000&#39; ms
  profilerSessionsLocation=&#39;C:\Users\jroque\AppData\Local\Temp\ConnectIoT\ProfilerSessions&#39;
  monitorProcessCommunication: useSsl=&#39;false&#39;
  driverProcessCommunication: useSsl=&#39;false&#39;
System:
  tenantName=&#39;BBRAUN&#39;, loadBalancing=&#39;false&#39;, urlSuffix=&#39;undefined&#39;
  address=&#39;localhost&#39;, port=&#39;903&#39;, ssl=&#39;false&#39;, timeout=&#39;120000 ms&#39;
  authentication: type=&#39;Password&#39;
    domain=&#39;cmf&#39;, user=&#39;jroque&#39;
11:23:15.36236 debug: This application is not allowed to use this transport (ManagerAndMonitor)
11:23:15.36236 debug: This application is not allowed to use this transport (Jobs)
11:23:15.36236 warn: *** Minimum log entries set to &#39;debug&#39; ***
11:23:15.36637 info: Starting NodeJS Controller with pid &#34;30428&#34;.
11:23:15.36637 debug: Command line: _=[], id=&#39;AutomationControllerInstance/2310311623180000011&#39;, managerId=&#39;ActiveTestManager&#39;, manager-id=&#39;ActiveTestManager&#39;, componentId=&#39;Controller&#39;, component-id=&#39;Controller&#39;, serverHost=&#39;localhost&#39;, monitorPort=64211, mp=64211, monitor-port=64211, monitorHost=&#39;localhost&#39;, mh=&#39;localhost&#39;, monitor-host=&#39;localhost&#39;, monitorToken=&#39;monitorSecurityToken&#39;, mt=&#39;monitorSecurityToken&#39;, monitor-token=&#39;monitorSecurityToken&#39;, monitorSsl=&#39;false&#39;, config=&#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;, dev=&#39;false&#39;, entityName=&#39;ControllerTest_FleetManager_CleanRoom&#39;, entity-name=&#39;ControllerTest_FleetManager_CleanRoom&#39;, port=13000, p=13000, $0=&#39;index.js&#39;
11:23:15.36637 info:   ConfigurationFile=&#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;
11:23:15.36737 info: Using &#39;C:/Users/jroque/Downloads/ActiveTestManager/ConnectIoT/Persistency&#39; for directory persistency storage. Retention time is &#39;2592000&#39; seconds
11:23:15.36737 info: Controller &#34;AutomationControllerInstance/2310311623180000011&#34; is booting...
11:23:15.36737 debug: Setting up MES system connection
11:23:15.36737 info: Setting up SystemAPI Rest communication
11:23:15.36737 debug: tenantName=&#39;BBRAUN&#39;, loadBalancing=&#39;false&#39;, address=&#39;localhost&#39;, port=&#39;903&#39;, ssl=&#39;false&#39;
11:23:15.36737 info: Authenticating client...
11:23:15.36837 debug: Password Authenticate: domain=&#39;cmf&#39;, username=&#39;jroque&#39;
11:23:15.46747 info: Authentication successful for user &#39;CMF\jroque&#39;
11:23:15.47147 info: Setting up Monitor connection
11:23:15.47147 debug: Establishing connection with Monitor, reconnectInterval=1000, reconnectMaxAttempts=0, host=&#39;localhost&#39;, port=64211, securityToken=&#39;monitorSecurityToken&#39;, requireBuffering=true, bufferingId=&#39;AutomationControllerInstance/2310311623180000011&#39;
11:23:15.47748 info: Connected with message bus!
11:23:15.47948 info: Connection with monitor established
11:23:15.47948 info: Connection with Monitor successfully established!
11:23:15.48048 debug: Executing query &#39;ControllerFromInstance&#39; in System, over entity &#39;AutomationControllerInstance&#39;
11:23:15.50350 info: Executed query, and received &#39;1&#39; results
11:23:17.54655 info: Adding workflow: Name=&#39;Setup&#39;, Id=&#39;2310311623180000702&#39;, LastServiceHistoryId=&#39;2310311623180044467&#39;
11:23:17.55355 info: Requesting package location. Package=&#39;@criticalmanufacturing/connect-iot-controller-engine-core-tasks&#39;, Version=&#39;10.1.2-202309224&#39;
11:23:17.55656 info: Requesting package location. Package=&#39;@criticalmanufacturing/connect-iot-controller-engine-opcua-tasks&#39;, Version=&#39;10.1.2-202309224&#39;
11:23:17.55656 info: Package location retrieved. Package=&#39;@criticalmanufacturing/connect-iot-controller-engine-core-tasks&#39;, Version=&#39;10.1.2-202309224&#39;, Location=&#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller-engine-core-tasks@10.1.2-202309224&#39;
11:23:18.21822 info: Package location retrieved. Package=&#39;@criticalmanufacturing/connect-iot-controller-engine-opcua-tasks&#39;, Version=&#39;10.1.2-202309224&#39;, Location=&#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller-engine-opcua-tasks@10.1.2-202309224&#39;
11:23:18.82382 info: Adding workflow: Name=&#39;RequestTransport_Command&#39;, Id=&#39;2310311623180000713&#39;, LastServiceHistoryId=&#39;2310311623180044467&#39;
11:23:18.82482 info: Requesting package location. Package=&#39;@criticalmanufacturing/connect-iot-controller-engine-factoryautomation-tasks&#39;, Version=&#39;10.1.2-202309224&#39;
11:23:18.82783 info: Package location retrieved. Package=&#39;@criticalmanufacturing/connect-iot-controller-engine-factoryautomation-tasks&#39;, Version=&#39;10.1.2-202309224&#39;, Location=&#39;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Cache\connect-iot-controller-engine-factoryautomation-tasks@10.1.2-202309224&#39;
11:23:19.14314 info: [&lt;&lt;root&gt;&gt;|RequestTransport_Command|task_2804|codeExecution] Initializing Virtual Machine Engine version 1.0.0
11:23:19.17217 info: Adding workflow: Name=&#39;RequestReleaseOfNextAction_Command&#39;, Id=&#39;2310311623180000709&#39;, LastServiceHistoryId=&#39;2310311623180044467&#39;
11:23:19.17518 info: Unsubscribing in &#39;Server&#39; a previously registered subject
11:23:19.17518 info: Subscribing in &#39;Server&#39; for messages of type &#39;connect.iot.driver.opcua.OnCommand.RequestReleaseOfNextAction&#39;
11:23:19.17518 info: Adding workflow: Name=&#39;JobStatusUpdate_Command&#39;, Id=&#39;2310311623180000707&#39;, LastServiceHistoryId=&#39;2310311623180044467&#39;
11:23:19.17818 info: Unsubscribing in &#39;Server&#39; a previously registered subject
11:23:19.17818 info: [&lt;&lt;root&gt;&gt;|JobStatusUpdate_Command|task_4493|codeExecution] Initializing Virtual Machine Engine version 1.0.0
11:23:19.18519 info: Subscribing in &#39;Server&#39; for messages of type &#39;connect.iot.driver.opcua.OnCommand.JobStatusUpdate&#39;
11:23:19.18519 info: Controller &#39;AutomationControllerInstance/2310311623180000011&#39; notifying monitor that is available. Pid=&#39;30428&#39;
11:23:19.18719 info: Received entity information assigned to this process. Name=&#39;ControllerTest_FleetManager_CleanRoom&#39;, Type=&#39;Cmf.Navigo.BusinessObjects.Resource, Cmf.Navigo.BusinessObjects&#39;
11:23:19.18819 info: Received connection information for &#34;Server&#34;
11:23:19.18819 debug: Details: type=&#39;websocket&#39;, host=&#39;localhost&#39;, port=64235, useSsl=false, securityToken=&#39;309f3390c5bf38dcba9c4c1764353621a579eb5b0a6183e09e93a2fd4d506083eec04e06241a62b910d65b7584288fa122cb314c1ce629ee34478d611255a92ccedf59de45d82b89d9ccd875062910c41530e82244a621eaae8259ac5942825babdaf2fef28f55177b1c9eaf6f2ae0939f04390fb6b4eb12a47f5ecc548934b69993cb2aedaafcad738a61b026ecff0b93142131d00de833cce4d50957aaaadc93fca8e942c304fe47fdcffb7ae2c0f7c15e3e37ec016e33778bfe41d6684ae90eeaf8dee4a247c1fd1ce374260678a5463fac42691099583f24025b17830381ac4ea1f4dea47132e9057f03b8994d6818460c2837f1bd4d16ad64833ba12842&#39;, $id=&#39;4&#39;
11:23:19.18819 info: Received connection information for &#34;Client&#34;
11:23:19.18819 debug: Details: type=&#39;websocket&#39;, host=&#39;localhost&#39;, port=64234, useSsl=false, securityToken=&#39;fef4e74349ebb1f03a4ea7f7e73fbc7f8cf770d37ec71f31b1fe3bd63e1fb9bcfb9cdf33fff7c914aaecd034d606e47369321e2275ea4e244657fe97952e09a05c92358dbcf959f22cbf5c1440a1f9f1edf685e5dc3d17ccd9e39a5dccd91a1fd085ab1895420d51884e226ba5e5ca464d2b0e6c071eaf235b76aa216c9cc9b370c7a72bd661ff98d3fc9c492bcb3ebca712ec235d68834ee815ccb1567d5a2a52a38c8884680e8c95e31308720cd36058e62f08c098a8d440923af8245ba18659db4afd715f7fb661aac00f8c7021b30b8746b889b692d3c7191f32349bab5a6e4e80616a38f96f316c2f1d295ac2d0ecd90c27681b730468421831d1230f58&#39;, $id=&#39;4&#39;
11:23:19.18919 info: Controller available notification was accepted
11:23:19.18919 debug: Updating &#39;AutomationControllerInstance/2310311623180000011&#39; instance to &#39;Running&#39;
11:23:19.28128 info: Updated &#39;AutomationControllerInstance/2310311623180000011&#39; instance to &#39;Running&#39;
11:23:19.28128 info: Controller process started with success
</code></pre><p>This log is a mirror to the monitor as some of the packages that were retrieved in the monitor were retrieved because the controller requested that it needed them. We also see the normal boot up with all the process configurations. There is the cycle of adding workflows and if those workflows require them, to generate subscriptions for particular types of messages.</p>
<p>Let&rsquo;s take a look at an example of receiving a command execution for an OPC-UA Server and then replying:</p>
<pre tabindex="0"><code class="language-log" data-lang="log">15:09:33.896 info: [25689036|JobStatusUpdate_Command|task_3478|onCommand] Received Command for OPC-UA Server with id:&#39;e61d71f3-c82b-4b53-8588-44febd241a16&#39; and values: &#39;[{&#34;name&#34;:&#34;TransportStatus&#34;,&#34;valueType&#34;:{&#34;type&#34;:4},&#34;editable&#34;:false,&#34;removable&#34;:false},{&#34;name&#34;:&#34;SLCID&#34;,&#34;valueType&#34;:{&#34;type&#34;:4},&#34;editable&#34;:false,&#34;removable&#34;:false},{&#34;name&#34;:&#34;AMRID&#34;,&#34;valueType&#34;:{&#34;type&#34;:4},&#34;editable&#34;:false,&#34;removable&#34;:false}]&#39;
15:09:33.903 debug: [25689036|JobStatusUpdate_Command|anyToConstant] Converted to constant value=&#34;0&#34;
15:09:33.903 debug: [25689036|JobStatusUpdate_Command|anyToConstant] Converted to constant value=&#34;No Error&#34;
15:09:33.922 debug: [25689036|JobStatusUpdate_Command|task_3479|commandResult] Sending command result for request with id e61d71f3-c82b-4b53-8588-44febd241a16.
15:09:33.922 info: Sending to &#39;Server&#39; a message of type &#39;connect.iot.driver.opcua.CommandResult&#39;
</code></pre><p>All this transaction works in the zone <code>25689036</code> <a href="https://help.criticalmanufacturing.com/userguide/business-data/automation-controller/tasks/zones/" target="_blank" rel="noopener">zones</a> <a href="https://www.npmjs.com/package/zone.js?activeTab=readme" target="_blank" rel="noopener">zone.js</a>. This id is very helpful to follow the transaction context throughout the logs. When the context is <code>&lt;root&gt;</code> it means no context was generated. We can see the name of the workflow page <code>JobStatusUpdate_Command</code> and the id and name of task <code>task_3478|onCommand</code>. Looking at the messages it&rsquo;s apparent we received a command with an id and a payload <code>id:'e61d71f3-c82b-4b53-8588-44febd241a16' and values: '[{&quot;name&quot;:&quot;TransportStatus&quot;,&quot;valueType&quot;:{&quot;type&quot;:4},&quot;editable&quot;:false,&quot;removable&quot;:false},{&quot;name&quot;:&quot;SLCID&quot;,&quot;valueType&quot;:{&quot;type&quot;:4},&quot;editable&quot;:false,&quot;removable&quot;:false},{&quot;name&quot;:&quot;AMRID&quot;,&quot;valueType&quot;:{&quot;type&quot;:4},&quot;editable&quot;:false,&quot;removable&quot;:false}]'</code>. Then we do some conversions of values and we reply back the command execution.</p>
<h3 id="automation-driver">Automation Driver</h3>
<p>The <code>Automation Driver</code> logs will describe the interface with a particular protocol.</p>
<pre tabindex="0"><code class="language-log" data-lang="log">14:21:20.04004 debug: Using file &#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39; for configurations
14:21:20.04204 debug: Registering configuration section &#39;logging&#39; (Logging section)
14:21:20.04204 debug: Registering configuration section &#39;driver&#39; (Driver Section)
14:21:20.04204 debug: Registering configuration section &#39;driver/processCommunication&#39; (Process communication with Controller (from driver))
14:21:20.04204 debug: Registering configuration section &#39;monitor/processCommunication&#39; (Process communication with Monitor)
14:21:20.04204 info: Parsing and validating configuration file &#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;
14:21:20.04404 debug: Using the following configuration settings:
Logging:
  Console (Console):
    level=&#39;debug&#39;, specificLevelLock=&#39;false&#39;, label=&#39;DriverOpcUA, format=&#39;${log.timestamp} ${log.level}: ${log.message}&#39;
    timestamp=&#39;true&#39;, timestampFormat=&#39;YYYY-MM-DD HH:mm:ss.SSS&#39;, maxLength=&#39;0&#39;
    colorize=&#39;true&#39;, colorizeMessage=&#39;true&#39;
    applications=&#39;&lt;All&gt;&#39;
  ControllersAndDrivers (File):
    level=&#39;debug&#39;, specificLevelLock=&#39;false&#39;, label=&#39;DriverOpcUA, format=&#39;${log.timestamp} ${log.level}: ${log.message}&#39;
    timestamp=&#39;true&#39;, timestampFormat=&#39;HH:mm:ss.SSSSS&#39;, maxLength=&#39;0&#39;
    dirname=&#39;C:/Users/jroque/Downloads/ActiveTestManager/ConnectIoT/Logs/Instances/ControllerTest_FleetManager_CleanRoom/Server&#39;, filename=&#39;DriverOpcUA_%DATE%.log&#39;, options=&#39;mode=511, flags=&#39;a&#39;&#39;
    datePattern=&#39;YYYY-MM-DD&#39;, maxSize=&#39;10m&#39;, maxFiles=&#39;30d&#39;, zippedArchive=&#39;false&#39;
    applications=&#39;[AutomationController, Driver*]&#39;
Driver(s):
  retryAttempts=&#39;30&#39;, sleepBetweenAttempts=&#39;1000&#39; ms
  processCommunication: useSsl=&#39;false&#39;
  monitorProcessCommunication: useSsl=&#39;false&#39;
14:21:20.06306 debug: This application is not allowed to use this transport (ManagerAndMonitor)
14:21:20.06306 debug: This application is not allowed to use this transport (Jobs)
14:21:20.06306 warn: *** Minimum log entries set to &#39;debug&#39; ***
14:21:20.06306 info: Starting OPC UA driver with pid &#34;21624&#34;.
14:21:20.06406 debug: Command line: _=[], id=&#39;AutomationDriverInstance/2310311623180000032&#39;, managerId=&#39;ActiveTestManager&#39;, manager-id=&#39;ActiveTestManager&#39;, componentId=&#39;Server&#39;, component-id=&#39;Server&#39;, serverHost=&#39;localhost&#39;, sh=&#39;localhost&#39;, server-host=&#39;localhost&#39;, monitorPort=49371, mp=49371, monitor-port=49371, monitorHost=&#39;localhost&#39;, mh=&#39;localhost&#39;, monitor-host=&#39;localhost&#39;, monitorToken=&#39;monitorSecurityToken&#39;, mt=&#39;monitorSecurityToken&#39;, monitor-token=&#39;monitorSecurityToken&#39;, monitorSsl=&#39;false&#39;, config=&#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;, dev=&#39;false&#39;, entityName=&#39;ControllerTest_FleetManager_CleanRoom&#39;, entity-name=&#39;ControllerTest_FleetManager_CleanRoom&#39;, inspect-brk=true, inspectBrk=true, serverPort=0, sp=0, server-port=0, $0=&#39;index.js&#39;
14:21:20.06406 info:   ConfigurationFile=&#39;C:\Users\jroque\Downloads\ActiveTestManager\config.json&#39;
14:21:20.06807 info: Driver &#34;AutomationDriverInstance/2310311623180000032&#34; is booting...
14:21:20.06907 info: Setting up Monitor connection
14:21:20.06907 debug: Establishing connection with Monitor, reconnectInterval=1000, host=&#39;localhost&#39;, port=49371, securityToken=&#39;monitorSecurityToken&#39;,
14:21:20.09009 info: Connection with monitor established
14:21:20.09109 info: Connection with Monitor successfully established!
14:21:20.10410 debug: Trying to read control persistency data from &#34;C:\Users\jroque\Downloads\ActiveTestManager\ConnectIoT\Persistency\OPCUA_Server\AutomationDriverInstance_2310311623180000032\connectIoT_ControlFile.json&#34;
14:21:20.10410 debug: No store was found
14:21:20.10410 info: Using port &#34;49380&#34; to communicate with controller
14:21:20.10410 info: Notifying communication details to allow connection with controller
14:21:20.10511 debug:   ConnectionInformation: type=&#39;websocket&#39;, host=&#39;localhost&#39;, port=49380, useSsl=false, securityToken=&#39;d5f6cdd28ecd535a221788046e56cd3989c3ea5db5a6399fbf098d871adc970369552b9d00cf16504461b868686cf921092ad59ffee3ccb3a56eb94911890d2a131ec25b0955db512f4cb741c62d209fa06a660ffa9698b8500d0fea46e33b03a9a9ced788eb34fee914fa923c8c2c66410c6a2d2f37f47efd056db945c24b76205fe1b125f2c976d1ade54bb4dbea016cb80bf83ee2f6f199274a0a8aad236a99574360154028d583e94c1e9a95c61b054cd268fbd7cadc58204a24ac52cfe96428f90a9f8e5b3e2c4e6e224f29bda81530a0b4ada244c1bf7cd9e078cb418604ce63f045cf31bfce4f8e2eb12434f069a1756bd7050465eb3cb74bb74c8cb6&#39;
14:21:20.11311 info: Received entity information assigned to this process. Name=&#39;ControllerTest_FleetManager_CleanRoom&#39;, Type=&#39;Cmf.Navigo.BusinessObjects.Resource, Cmf.Navigo.BusinessObjects&#39;
14:21:20.11411 info: Communication details notification was accepted
14:21:20.11411 info: OPC UA Driver process started with success
14:21:25.70070 debug: Client Connected! from 49967. Have now 1 clients.
14:21:25.71471 info: Configuring device based on configuration received
14:21:25.71872 debug: Managing certificates
14:21:25.71972 debug: Certificates folder: C:\Users\jroque\AppData\Local\Temp\ConnectIoT\OPC-UA\Certificates\AutomationDriverInstance_2310311623180000032
14:21:25.71972 debug: No certificate/public key define, using default generated.
14:21:25.94194 debug: Added custom data types
14:21:26.18819 info: Configuring device based on configuration received
14:21:26.18919 debug: Managing certificates
14:21:26.19019 debug: Certificates folder: C:\Users\jroque\AppData\Local\Temp\ConnectIoT\OPC-UA\Certificates\AutomationDriverInstance_2310311623180000032
14:21:26.19019 debug: No certificate/public key define, using default generated.
14:21:26.19319 info: Connecting device
14:21:26.19419 info: Creating OPCUA server
14:21:26.19620 info: Communication state changed Unknown -&gt; Connecting
14:21:26.19720 info: Initializing OPCUA server
14:21:27.14414 info: Starting OPCUA server
14:21:27.14715 info: Server listening on &#39;opc.tcp://127.0.0.1:6020&#39;
14:21:27.14715 info: Communication state changed Connecting -&gt; Setup
14:21:27.14715 info: Device connected and ready to be setup
14:21:27.17618 info: Reporting Setup Result (success=&#39;true&#39;)
14:21:27.17618 info: Device successfully setup
14:21:27.17618 info: Communication state changed Setup -&gt; Communicating
</code></pre><p>Very similar to the controller there is a boot up cycle. Interesting things that we see is that we have a persistency being retrieved for the driver, also that this driver uses certificates and that is listening on <code>opc.tcp://127.0.0.1:6020</code>.</p>
<p><strong>Note</strong>: All drivers are different and log different things, in this case it is a Server as such it informs where the server is hosting.</p>
<p>Notice that it connects to the Monitor application and also to the controller. The connection via websocket to the controller is how we will notify it of new information (i.e events).</p>
<pre tabindex="0"><code class="language-log" data-lang="log">14:22:05.43544 info: Received a Command Execution for &#39;JobStatusUpdate&#39; with id:&#39;8ee8ad50-314c-4c31-a0a1-cc9437a14ea1&#39; with command parameters &#39;{&#34;TransportStatus&#34;:&#34;Completed&#34;,&#34;SLCID&#34;:&#34;test&#34;,&#34;AMRID&#34;:&#34;asdas&#34;}&#39;
14:22:05.43544 debug: Notify connect.iot.driver.opcua.OnCommand.JobStatusUpdate for &#39;JobStatusUpdate&#39; with command params &#39;{&#34;TransportStatus&#34;:&#34;Completed&#34;,&#34;SLCID&#34;:&#34;test&#34;,&#34;AMRID&#34;:&#34;asdas&#34;}&#39;
14:22:18.31932 debug: Heartbeat received from Monitor
14:22:27.19720 info: Getting values for properties: JobStatusUpdate-HServiceType
14:22:28.11500 info: Replying to command with id &#39;8ee8ad50-314c-4c31-a0a1-cc9437a14ea1&#39; with outputs: [{&#34;dataType&#34;:5,&#34;value&#34;:0,&#34;arrayType&#34;:0},{&#34;dataType&#34;:12,&#34;value&#34;:&#34;No Error&#34;,&#34;arrayType&#34;:0}]
</code></pre><p>In this log we see that there was a command execution in the OPC-UA server. The command was the <code>JobStatusUpdate</code> and it was sent with the parameters <code>{&quot;TransportStatus&quot;:&quot;Completed&quot;,&quot;SLCID&quot;:&quot;test&quot;,&quot;AMRID&quot;:&quot;asdas&quot;}</code>. Then we see this request being forwarded to the controller. We get a request and we answer with a reply for the same id <code>8ee8ad50-314c-4c31-a0a1-cc9437a14ea1</code>.</p>
<p>Pretty simple, right. Let&rsquo;s look at the workflow to see if it matches.</p>
<p>
<img src="/blogPosts/IoT/20231206_iot_logging/iot_logging_workflow.png" alt="Workflow" />

</p>
<p>All in all, we just receive a request and reply with a constant set of values. But you already new this, as we already took that trip through the logs!!!</p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Blog Contributions</title><link>https://devblog.criticalmanufacturing.com/blog/20230915_contributions/</link><pubDate>Fri, 15 Sep 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230915_contributions/</guid><description>How To Contribute to the DevBlog</description><content:encoded><![CDATA[<p>We will explain how you can contribute to the devblog.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview</li>
<li>Writing a Post</li>
<li>Deployment</li>
<li>Future</li>
</ol>
<h2 id="thank-you">Thank you</h2>
<p>If you are reading this, you are thinking of contributing, so you are already half way there 😉. A blog only survives by the quality and volume of content, so scalability and diversity of content are key.</p>
<p>&ldquo;But why will I contribute, tomorrow we move to another platform and everything is lost&rdquo; - Well, this is a legitimate concern, in the past we had this happen to us, when we relied on proprietary systems to store our information, only to later realize exporting information was a major hassle. But fret not, this time we use markdown for all this content, so if tomorrow we want to move to an even better platform due to having so much content and contributors, we can just tweak a bit and use as is.</p>
<h2 id="overview">Overview</h2>
<p>The DevBlog is built in <a href="https://gohugo.io/" target="_blank" rel="noopener">hugo</a>, using a template <a href="https://www.heksagon.net/tags/hugo/" target="_blank" rel="noopener">heksagon</a>. The source code resides in <a href="https://tfs-product.cmf.criticalmanufacturing.com/Products/Product/_git/DevDocs?version=GBdevelopment-88269-devblog" target="_blank" rel="noopener">DevDocs</a> under the folder <code>DevBlogPortal</code>.</p>
<p>In order to start locally the blog, install <code>hugo</code> in a powershell run <code>choco install hugo-extended</code>, then simply clone the repository, open a command shell in the folder <code>DevBlogPortal</code> and run <code>hugo serve</code>. This is very helpful to validate how your post looks before commiting the changes, you can also perform edits while the server runs and it will refresh automatically.</p>
<p>The important folders for contributors are the <code>DevBlogPortal/content/blog</code> here is where you will create new contributions are write the markdown for your blog post and <code>DevBlogPortal/static/blogPosts</code> here is where all static content will reside (i.e images).</p>
<p><strong>Note</strong>: Please use videos/gifs with moderation as they cumulatively consume a lot of space.</p>
<h2 id="writing-a-post">Writing a Post</h2>
<p>To write the markdowns there are a lot of available markdown editors, what was used to write this post as VSCode, VSCode has a preview mode for markdown.</p>
<p>In order to write a new post go to <code>DevBlogPortal/content/blog</code> and create a new markdown file with the name <code>&lt;yearmonthday&gt;_&lt;areaofinterest&gt;_&lt;yourpost&gt;</code> (i.e 20230413_iot_mes_as_configuration_owner). It&rsquo;s very important your markdown has the following <code>header</code>:</p>
<pre tabindex="0"><code>---
author: &lt;authorname&gt;
title: &lt;title&gt;
date: &lt;yearmonthday&gt;
description: &lt;description
tags: [&lt;tag1&gt;,&lt;tag2&gt;]
categories: [&lt;category1&gt;&lt;category2&gt;]
ShowToc: true
TocOpen: true
---
</code></pre><p>It&rsquo;s very important you add relevant tags and categories as the template we are using, offers a very powerful search based on tags and categories, and as the number of post grows it is a very easy way to sort and search.</p>
<p>We also strongly incentivize for you to add a <code>footer</code></p>
<pre tabindex="0"><code>
&lt;br&gt;&lt;/br&gt;

---

&lt;br&gt;&lt;/br&gt;
## Author
###  &lt;Small text saluting the reader and explaining who you are and how they can reach you&gt;

Skills: &lt;particular focus or areas of interest you have&gt;
&lt;image of yourself (use your cmf photo if possible) this photos should be added under `DevBlogPortal/static/blogPosts/contributors`&gt;
</code></pre><h3 id="things-to-look-out-for">Things to look out for</h3>
<p>The least intuitive thing, is that for images, the path for deployment and for preview here in your markdown editor are different. For example, it would be intuitive to say that an image is under <code>DevBlogPortal/static/blogPosts/contributors/johnDoe</code>.jpg, but when hugo deploy this path will not have meaning, you should use <code>/blogPosts/contributors/johnDoe.jpg</code>. Nevertheless, you can use <code>hugo serve</code> in order to see how the site will render.</p>
<p>When you&rsquo;ve finished writing your post and are happy with the result, create a <code>Pull Request</code> against the branch <a href="https://tfs-product.cmf.criticalmanufacturing.com/Products/Product/_git/DevDocs?version=GBdevelopment-88269-devblog" target="_blank" rel="noopener">DevDocs</a>. Someone from the DevDocs team will revise your post and perform the deployment.</p>
<h2 id="future">Future</h2>
<p>The CMF CLI already has a cmf doc-gen, so why not add a doc-gen for the blog, that creates this structure! As more contributors join, we get their feedback and understand how needed is this type of tooling.</p>
<p><br></br></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Connect IoT Extensibility</title><link>https://devblog.criticalmanufacturing.com/blog/20230911_iot_extensibility_get_props/</link><pubDate>Mon, 11 Sep 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230911_iot_extensibility_get_props/</guid><description>Dynamically Get Properties using IoT Extensibility</description><content:encoded><![CDATA[<p>It will be shown how to leverage Connect IoT extensibility for a more dynamic implementation. This will show a more advanced feature of Connect IoT.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview</li>
<li>Use Case IoT</li>
<li>Use Case MES (extra)</li>
</ol>
<h2 id="overview">Overview</h2>
<p>Connect IoT is not just customizable, but it is also <strong>extendable</strong>. This means that you have entry points that are already given out of the box, for a more advanced implementation that can leverage the system. For example, in the automation controller the code task, provides a point of extensibility where you can create a task with your own set of code. This is a rather straightforward point of extensibility, but also the <strong>driver is extensible</strong> and the use of both together is what is going to be shown today.</p>
<p>The driver is traditionally described in the Automation Driver Definition, where you will complete all the required communication interface with a particular equipment in a given protocol. Nevertheless, it may be the case where you may not want to have such a static definition, and may want to resolve what to subscribe or get by logic outside of the normal driver definition.</p>
<p>This is an <strong>advanced scenario as you are skipping the normal system behavior</strong> and are replacing this mechanism with your own.</p>
<p>The use case is on Track In, depending on the attached resource instrument, we want to dynamically resolve which properties to retrieve and post to a <code>Data Collection</code>. So, in this case we don&rsquo;t know before hand what are the properties we want to retrieve. The business logic is the owner of what the system must retrieve and is even orchestrated outside the Automation Controller.</p>
<p>The implementation will be done using OPC-UA as protocol, but should be more or less generic to every protocol.</p>
<h2 id="use-case-iot">Use Case IoT</h2>
<p>We will try to focus first on the use case from a <code>Connect IoT</code> perspective and will then explain a bit more what was the full feature, using the MES. From an IoT perspective the use case will be that we require on Track In to dynamically <code>Get Properties</code> and post to a <code>DataCollectionInstance</code> opened for a <code>Material</code>.</p>
<h3 id="create-a-dee-in-track-in">Create a DEE in Track In</h3>
<p>We will have a DEE that will publish to IoT on the topic <code>Cmf.Trackin.LineClearance</code> with an object like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;materialName&#34;</span>: <span style="color:#e6db74">&#34;materialExample&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;parameterTag&#34;</span>:
</span></span><span style="display:flex;"><span>        { <span style="color:#f92672">&#34;Temperature&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tag&#34;</span>: <span style="color:#e6db74">&#34;ns=3;s=AirConditioner_1.Temperature&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;deviceType&#34;</span>: <span style="color:#e6db74">&#34;Long&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;dataType&#34;</span>: <span style="color:#e6db74">&#34;Double&#34;</span>,
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">&#34;Pressure&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;tag&#34;</span>: <span style="color:#e6db74">&#34;ns=3;s=AirConditioner_1.Pressure&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;deviceType&#34;</span>: <span style="color:#e6db74">&#34;Long&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;dataType&#34;</span>: <span style="color:#e6db74">&#34;Double&#34;</span>,
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then the DEE must be something like this (here we show the snippet pertinent to IoT), it will collect the material name and then for each parameter collect all the necessary information to be able to get the property and post to a datacollection.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>(...)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> parameters = dc.DataCollectionParameters.Select(dcp =&gt; dcp.TargetEntity.Name);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// This is the object that will hold the parameter and as value, everything required to perform a get</span>
</span></span><span style="display:flex;"><span>JObject parameterTag = <span style="color:#66d9ef">new</span> JObject();
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">foreach</span> (DataRow row <span style="color:#66d9ef">in</span> ds.Tables[<span style="color:#ae81ff">0</span>].Rows)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> rowParam = row[<span style="color:#e6db74">&#34;Parameter&#34;</span>].ToString();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (parameters.Contains(rowParam))
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        JObject tagInfo = <span style="color:#66d9ef">new</span> JObject();
</span></span><span style="display:flex;"><span>        tagInfo[<span style="color:#e6db74">&#34;tag&#34;</span>] = row[<span style="color:#e6db74">&#34;DeviceID&#34;</span>].ToString();
</span></span><span style="display:flex;"><span>        tagInfo[<span style="color:#e6db74">&#34;deviceType&#34;</span>] = row[<span style="color:#e6db74">&#34;DeviceType&#34;</span>].ToString();
</span></span><span style="display:flex;"><span>        tagInfo[<span style="color:#e6db74">&#34;dataType&#34;</span>] = row[<span style="color:#e6db74">&#34;DataType&#34;</span>].ToString();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        parameterTag[row[<span style="color:#e6db74">&#34;Parameter&#34;</span>].ToString()] = tagInfo;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// This code implies there is an Instance associated between the Resource, the Controller and Driver</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> instance = resource.GetAutomationControllerInstance();
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (instance == <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Exception(<span style="color:#e6db74">&#34;Resource not connected to any IoT instance&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    JObject data = <span style="color:#66d9ef">new</span> JObject();
</span></span><span style="display:flex;"><span>    data[<span style="color:#e6db74">&#34;materialName&#34;</span>] = material.Name;
</span></span><span style="display:flex;"><span>    data[<span style="color:#e6db74">&#34;parameterTag&#34;</span>] = parameterTag;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">string</span> topic = <span style="color:#e6db74">&#34;Cmf.Trackin.LineClearance&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// This Code will publish a message bus broadcast for the specified topic, with the specified data</span>
</span></span><span style="display:flex;"><span>    instance.Publish(topic, data);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Note</strong>: The payloads being sent in the MessageBus should be the <strong>least required for the implementation</strong>. Beware of sending big payloads, creating traffic, without a very strong reason, if you fall in this case, you are probably doing something wrong in terms of component coupling architecture.</p>
<h3 id="looking-at-the-automation-controller">Looking at the Automation Controller</h3>
<p>The implementation in Connect IoT, will now be split in to four parts: Receiving Messages, Transform Data, Dynamically Get Properties, Post to Data Collection.</p>
<h4 id="receiving-messages">Receiving Messages</h4>
<p>Now we have a DEE that publishes a message with topic <code>Cmf.Trackin.LineClearance</code> on TrackIn with the information regarding the material tracked in and what parameters to collect and post. Now we need to add a subscriber for that topic by using a <code>On System Event</code> <a href="https://help.criticalmanufacturing.com/userguide/business-data/automation-controller/tasks/core/task_systemevent/" target="_blank" rel="noopener">Documentation</a>, this task will be notified whenever a message is published in the MessageBus for this topic.</p>
<p>We have created a link between the information in the DEE and our workflow.</p>
<h4 id="transform-data">Transform Data</h4>
<p>We will use two <code>Code</code> tasks to transform the data from the <code>parameterTags</code> provided by the DEE into the input <code>content</code> the <code>Send Notification To Driver</code> is expecting. Then we will transform that information into what is needed to successfully post the data.</p>
<h5 id="create-the-content-object-to-send-to-the-driver">Create the content object to send to the driver</h5>
<p>In this case we are not worried about the extended data, so we will just use the default.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Framework</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;framework&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">class</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    (<span style="color:#75715e">/** Allows accessing external functions */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">framework</span>: <span style="color:#66d9ef">Framework</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">constructor</span>(<span style="color:#a6e22e">framework</span>: <span style="color:#66d9ef">Framework</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">framework</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">framework</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/*
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * Entry point of the class (IMPORTANT: don&#39;t change the signature of this method)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * Should return an object containing the values for each output to emit
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * If necessary, use the parameter &#34;outputs&#34; to emit data while running the code.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     */</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">async</span> <span style="color:#a6e22e">main</span>(<span style="color:#a6e22e">inputs</span>: <span style="color:#66d9ef">any</span>, <span style="color:#a6e22e">outputs</span>: <span style="color:#66d9ef">any</span>)<span style="color:#f92672">:</span> <span style="color:#a6e22e">Promise</span>&lt;<span style="color:#f92672">any</span>&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">values</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Map</span>&lt;<span style="color:#f92672">string</span><span style="color:#960050;background-color:#1e0010">,</span> <span style="color:#a6e22e">Tag</span>&gt;(Object.<span style="color:#a6e22e">entries</span>(<span style="color:#a6e22e">inputs</span>.<span style="color:#a6e22e">parameterTag</span>));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">content</span> <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">value</span>] <span style="color:#66d9ef">of</span> <span style="color:#a6e22e">values</span>.<span style="color:#a6e22e">entries</span>()) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">prop</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">name</span>: <span style="color:#66d9ef">value.tag</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">deviceId</span>: <span style="color:#66d9ef">value.tag</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">deviceType</span>: <span style="color:#66d9ef">value.deviceType</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">dataType</span>: <span style="color:#66d9ef">value.dataType</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">extendedData</span>: <span style="color:#66d9ef">value?.extendedData</span> <span style="color:#f92672">??</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">parse</span>(<span style="color:#e6db74">&#34;{\&#34;$id\&#34;:\&#34;1\&#34;,\&#34;customDataType\&#34;:\&#34;\&#34;,\&#34;arrayType\&#34;:\&#34;Scalar\&#34;,\&#34;dimensions\&#34;:\&#34;\&#34;,\&#34;path\&#34;:\&#34;\&#34;,\&#34;isPersisted\&#34;:\&#34;false\&#34;,\&#34;defaultValue\&#34;:\&#34;\&#34;,\&#34;minimumSamplingInterval\&#34;:\&#34;1000\&#34;}&#34;</span>)
</span></span><span style="display:flex;"><span>            };
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">content</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">prop</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">content</span>.<span style="color:#a6e22e">emit</span>(<span style="color:#a6e22e">content</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">parameterTag</span>.<span style="color:#a6e22e">emit</span>(<span style="color:#a6e22e">inputs</span>.<span style="color:#a6e22e">parameterTag</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">Tag</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tag</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">deviceType</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">dataType</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">extendedData</span>: <span style="color:#66d9ef">any</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h5 id="create-the-rawdata-object-to-send-to-the-datacollection-task">Create the rawData object to send to the DataCollection Task</h5>
<p>The translation from what we receive from the <code>Send Notification To Driver</code> into rawData of the task <code>Data Collection</code> <a href="https://help.criticalmanufacturing.com/userguide/business-data/automation-controller/tasks/mes/task_datacollection/" target="_blank" rel="noopener">Documentation</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Framework</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;framework&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#66d9ef">class</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/** Allows accessing external functions */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">framework</span>: <span style="color:#66d9ef">Framework</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">constructor</span>(<span style="color:#a6e22e">framework</span>: <span style="color:#66d9ef">Framework</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">framework</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">framework</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/*
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * Entry point of the class (IMPORTANT: don&#39;t change the signature of this method)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * Should return an object containing the values for each output to emit
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * If necessary, use the parameter &#34;outputs&#34; to emit data while running the code.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">async</span> <span style="color:#a6e22e">main</span>(<span style="color:#a6e22e">inputs</span>: <span style="color:#66d9ef">any</span>, <span style="color:#a6e22e">outputs</span>: <span style="color:#66d9ef">any</span>)<span style="color:#f92672">:</span> <span style="color:#a6e22e">Promise</span>&lt;<span style="color:#f92672">any</span>&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">values</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Map</span>&lt;<span style="color:#f92672">string</span><span style="color:#960050;background-color:#1e0010">,</span> <span style="color:#a6e22e">Tag</span>&gt;(Object.<span style="color:#a6e22e">entries</span>(<span style="color:#a6e22e">inputs</span>.<span style="color:#a6e22e">parameterTag</span>));
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">reply</span> <span style="color:#f92672">=</span> Array.<span style="color:#66d9ef">from</span>(<span style="color:#a6e22e">inputs</span>.<span style="color:#a6e22e">reply</span>) <span style="color:#66d9ef">as</span> Array&lt;<span style="color:#f92672">TagReply</span>&gt;;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">dcp</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Map</span>&lt;<span style="color:#f92672">string</span><span style="color:#960050;background-color:#1e0010">,</span> <span style="color:#a6e22e">DataCollectionPoint</span>&gt;();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">const</span> [<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">value</span>] <span style="color:#66d9ef">of</span> <span style="color:#a6e22e">values</span>.<span style="color:#a6e22e">entries</span>()) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">replyValue</span> <span style="color:#66d9ef">of</span> <span style="color:#a6e22e">reply</span>) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">value</span>.<span style="color:#a6e22e">tag</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">replyValue</span>.<span style="color:#a6e22e">propertyName</span>) { 
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">point</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">value</span>: <span style="color:#66d9ef">replyValue.value</span>,
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">sampleId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Sample 1&#34;</span>,
</span></span><span style="display:flex;"><span>                    };
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">dcp</span>.<span style="color:#66d9ef">set</span>(<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">point</span>);
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">dataCollectionPoints</span>.<span style="color:#a6e22e">emit</span>(<span style="color:#a6e22e">dcp</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">Tag</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tag</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">deviceType</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">dataType</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">extendedData</span>: <span style="color:#66d9ef">any</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">TagReply</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">propertyName</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">originalValue</span>: <span style="color:#66d9ef">any</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">value</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">DataCollectionPoint</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">value</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">sampleId</span>: <span style="color:#66d9ef">any</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="dynamically-get-properties">Dynamically Get Properties</h4>
<p>We will now dynamically invoke the <code>Send Notification to Driver</code> <a href="https://help.criticalmanufacturing.com/userguide/business-data/automation-controller/tasks/core/task_sendnotificationtodriver/" target="_blank" rel="noopener">Documentation</a>. We can use the extensibility to use in runtime, without having to pre declare in the driver definition all the information regarding events, commands and properties. In this case, we will dynamically GetProperties.</p>
<hr>
<h3 id="extensibility">Extensibility</h3>
<p>If we check the documentation for OPC-UA <a href="https://help.criticalmanufacturing.com/userguide/business-data/automation-protocol/automation-protocol-protocols/driver_opcua/#get-properties-values" target="_blank" rel="noopener">Documentation</a>, in the section under methods, it describes how we can use the extensibility feature. In this case we want to get properties, to collect OPC-UA tags. In order to do that, we need to send a <code>Request</code> for the driver with the topic <code>connect.iot.driver.opcua.getPropertiesValues</code> and as the documentation explains, it requires a <code>name</code>, <code>deviceId</code>, <code>dataType</code>, <code>deviceType</code>.</p>
<hr>
<p>In order to use the extension entry point we could invoke directly from the <code>Code Task</code>, in this case we want to separate the business logic, so we will use the default task for this <code>Send Notification To Driver</code>. We will connect the data from the <code>On System Event</code> to the <code>Code Task</code> retrieving the object <code>parameterTag</code>. Notice these are the keys of the object created in the DEE.</p>
<hr>
<h4 id="using-only-the-code-task">Using Only the Code Task</h4>
<p>
<img src="/blogPosts/IoT/20230911_iot_extensibility_get_props/iot_extensibility_get_prop_workflow_codetask.png" alt="Using Only the Code Task" />

</p>
<hr>
<h4 id="post-to-data-collection">Post to Data Collection</h4>
<p>Now we have everything we need to perform a post to the material <code>DataCollectionInstance</code>, we use a <code>Synchronize</code> task, to make sure we have everything ready when we post. We use the converter <code>Get Object Property</code> path: <code>materialName</code>, type <code>string</code>, to retrieve the material name from the <code>data</code> output and then the converter <code>Create System Entity Instance</code>, with entityType <code>Material</code> and identifier <code>Name</code> to create a shell object of the <code>Material</code>. The <code>Data Collection</code> task will receive a material and the rawData corresponding the to <code>dataCollectionPoints</code> emitted by the code task.</p>
<p>
<img src="/blogPosts/IoT/20230911_iot_extensibility_get_props/iot_extensibility_get_prop_workflow.png" alt="Workflow" />

</p>
<p>This was to show how you can dynamically get properties, similar variations exist for all actions, set properties, execute commands and also notify and register events.</p>
<h2 id="use-case-mes">Use Case MES</h2>
<p>We will now take some time to explain the full use case.</p>
<p>In this example we will create a generic table that relates Resource &lt;-&gt; Parameter &lt;-&gt; Device Id &lt;-&gt; DataType &lt;-&gt; DeviceType. In the TrackIn, we will retrieve the resource instruments attached to the resource and resolve the table, in order to retrieve the Device Ids to collect from the OPC-UA server. Then invoke Connect IoT to collect and post to the <code>DataCollectionInstance</code> that was opened in the TrackIn for the material.</p>
<h3 id="create-a-generic-table">Create a Generic Table</h3>
<p>We created a <code>Generic Table</code> with the Resource (Name) and Parameter (Name) as keys, the DeviceId and DeviceType as text fields and the DataType as the <code>Enum Cmf.Foundation.BusinessObjects.AutomationDataType</code>. The DeviceType is part of the extension data of the Protocol, so we have no way of providing that information for the table, without being protocol specific.</p>
<p>
<img src="/blogPosts/IoT/20230911_iot_extensibility_get_props/iot_extensibility_get_prop_gt.png" alt="Generic Table Definition" />

</p>
<p>We can add some data to the table matching resource instruments, parameters and OPC-UA tags.</p>
<p>
<img src="/blogPosts/IoT/20230911_iot_extensibility_get_props/iot_extensibility_get_prop_gt_data.png" alt="Generic Table Data" />

</p>
<h3 id="create-a-dee-in-track-in-1">Create a DEE in Track In</h3>
<p>We will now create a DEE that will, resolve the instruments attached to the resource, and correlate the CurrentDataCollectionInstance of the Material with the parameters defined in the table for the attached instruments.</p>
<p>Here we are using some specific project utilities, but bellow the pertinent code is shown.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;System.Data.dll&#34;</span>, <span style="color:#e6db74">&#34;System.Data&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Cmf.Foundation.BusinessObjects.dll&#34;</span>, <span style="color:#e6db74">&#34;Cmf.Foundation.BusinessObjects&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Cmf.Foundation.BusinessOrchestration.dll&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#e6db74">&#34;Cmf.Foundation.Common.Exceptions&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#e6db74">&#34;Cmf.Foundation.Common&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Cmf.Navigo.BusinessObjects.dll&#34;</span>, <span style="color:#e6db74">&#34;Cmf.Navigo.BusinessObjects&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Cmf.Navigo.BusinessOrchestration.dll&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Cmf.Navigo.BusinessOrchestration.dll&#34;</span>, <span style="color:#e6db74">&#34;Cmf.Navigo.BusinessOrchestration.MaterialManagement.InputObjects&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Newtonsoft.Json.dll&#34;</span>, <span style="color:#e6db74">&#34;Newtonsoft.Json.Linq&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;%MicrosoftNetPath%Microsoft.CSharp.dll&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Cmf.Custom.Project.Common.dll&#34;</span>, <span style="color:#e6db74">&#34;Cmf.Custom.Project.Common&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Cmf.Common.CustomActionUtilities.dll&#34;</span>, <span style="color:#e6db74">&#34;Cmf.Common.CustomActionUtilities&#34;</span>);
</span></span><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Cmf.Custom.Project.Common.dll&#34;</span>, <span style="color:#e6db74">&#34;Cmf.Custom.Project.Common&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>IMaterial material = DeeContextHelper.GetContextParameter(<span style="color:#e6db74">&#34;material&#34;</span>) <span style="color:#66d9ef">as</span> IMaterial;
</span></span><span style="display:flex;"><span>IResource resource = DeeContextHelper.GetContextParameter(<span style="color:#e6db74">&#34;resource&#34;</span>) <span style="color:#66d9ef">as</span> IResource;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>material.Load();
</span></span><span style="display:flex;"><span>resource.Load();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Retrieve Resource Instruments</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span>(resource.RelationCollection == <span style="color:#66d9ef">null</span> || !resource.RelationCollection.ContainsKey(<span style="color:#e6db74">&#34;ResourceInstrument&#34;</span>)) {
</span></span><span style="display:flex;"><span>    resource.LoadRelations(<span style="color:#e6db74">&#34;ResourceInstrument&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> filters = <span style="color:#66d9ef">new</span> List&lt;IFilter&gt;();
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span>(resource.RelationCollection != <span style="color:#66d9ef">null</span> &amp;&amp; resource.RelationCollection.ContainsKey(<span style="color:#e6db74">&#34;ResourceInstrument&#34;</span>)) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">foreach</span>(ResourceInstrument item <span style="color:#66d9ef">in</span> resource.RelationCollection[<span style="color:#e6db74">&#34;ResourceInstrument&#34;</span>]) {
</span></span><span style="display:flex;"><span>        filters.Add(<span style="color:#66d9ef">new</span> Foundation.BusinessObjects.QueryObject.Filter()
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            Name = <span style="color:#e6db74">&#34;Resource&#34;</span>,
</span></span><span style="display:flex;"><span>            Operator = Cmf.Foundation.Common.FieldOperator.IsEqualTo,
</span></span><span style="display:flex;"><span>            Value = item.TargetEntity.Name,
</span></span><span style="display:flex;"><span>            LogicalOperator = Cmf.Foundation.Common.LogicalOperator.OR
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>DataSet ds = Cmf.Custom.Project.Common.ProjectUtilities.GetGenericTableData(<span style="color:#e6db74">&#34;CustomIoTDataCollectionContext&#34;</span>, filters.ToArray());
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (ds.HasData())
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    IDataCollectionInstance dataCollectionInstance = material.CurrentDataCollectionInstance;
</span></span><span style="display:flex;"><span>    dataCollectionInstance.Load();
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    IDataCollection dc = dataCollectionInstance.DataCollection <span style="color:#66d9ef">as</span> IDataCollection;
</span></span><span style="display:flex;"><span>    dc.LoadRelations(<span style="color:#e6db74">&#34;DataCollectionParameter&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(!dc.DataCollectionParameters.Any()){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Exception(<span style="color:#e6db74">&#34;No Parameters found&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> parameters = dc.DataCollectionParameters.Select(dcp =&gt; dcp.TargetEntity.Name);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// This is the object that will hold the parameter and as value, everything required to perform a get</span>
</span></span><span style="display:flex;"><span>    JObject parameterTag = <span style="color:#66d9ef">new</span> JObject();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">foreach</span> (DataRow row <span style="color:#66d9ef">in</span> ds.Tables[<span style="color:#ae81ff">0</span>].Rows)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> rowParam = row[<span style="color:#e6db74">&#34;Parameter&#34;</span>].ToString();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (parameters.Contains(rowParam))
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            JObject tagInfo = <span style="color:#66d9ef">new</span> JObject();
</span></span><span style="display:flex;"><span>            tagInfo[<span style="color:#e6db74">&#34;tag&#34;</span>] = row[<span style="color:#e6db74">&#34;DeviceID&#34;</span>].ToString();
</span></span><span style="display:flex;"><span>            tagInfo[<span style="color:#e6db74">&#34;deviceType&#34;</span>] = row[<span style="color:#e6db74">&#34;DeviceType&#34;</span>].ToString();
</span></span><span style="display:flex;"><span>            tagInfo[<span style="color:#e6db74">&#34;dataType&#34;</span>] = row[<span style="color:#e6db74">&#34;DataType&#34;</span>].ToString();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            parameterTag[row[<span style="color:#e6db74">&#34;Parameter&#34;</span>].ToString()] = tagInfo;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// This code implies there is an Instance associated between the Resource, the Controller and Driver</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> instance = resource.GetAutomationControllerInstance();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (instance == <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Exception(<span style="color:#e6db74">&#34;Resource not connected to any IoT instance&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        JObject data = <span style="color:#66d9ef">new</span> JObject();
</span></span><span style="display:flex;"><span>        data[<span style="color:#e6db74">&#34;materialName&#34;</span>] = material.Name;
</span></span><span style="display:flex;"><span>        data[<span style="color:#e6db74">&#34;parameterTag&#34;</span>] = parameterTag;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">string</span> topic = <span style="color:#e6db74">&#34;Cmf.Trackin.LineClearance&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// This Code will publish a message bus broadcast for the specified topic, with the specified data</span>
</span></span><span style="display:flex;"><span>        instance.Publish(topic, data);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This is the code pertaining to <code>Cmf.Custom.Project.Common.ProjectUtilities.GetGenericTableData</code>, invoked in the DEE.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;summary&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// Returns a dataset with the filtered data from a GenericTable</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;/summary&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;param name=&#34;table&#34;&gt;The Generic Table to filter&lt;/param&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;param name=&#34;filters&#34;&gt;The filters to apply&lt;/param&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;returns&gt;A DataSet with the filtered content, null otherwise&lt;/returns&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">static</span> DataSet GetGenericTableData(IGenericTable table, <span style="color:#66d9ef">params</span> IFilter[] filters)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    DataSet output = <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (table != <span style="color:#66d9ef">null</span> &amp;&amp; table.Id &gt; <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        IFilterCollection tableFilters = <span style="color:#66d9ef">new</span> FilterCollection();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (filters != <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            tableFilters.AddRange(filters);
</span></span><span style="display:flex;"><span>            table.LoadData(tableFilters);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            table.LoadData();
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (table.Data != <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            output = NgpDataSet.ToDataSet(table.Data);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> output;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;summary&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// Returns a dataset with the filtered data from a GenericTable</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;/summary&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;param name=&#34;tableName&#34;&gt;The name of the Generic Table&lt;/param&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;param name=&#34;filters&#34;&gt;The filters to apply&lt;/param&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/// &lt;returns&gt;&lt;/returns&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">static</span> DataSet GetGenericTableData(<span style="color:#66d9ef">string</span> tableName, <span style="color:#66d9ef">params</span> IFilter[] filters)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    IGenericTable table = <span style="color:#66d9ef">new</span> GenericTable();
</span></span><span style="display:flex;"><span>    table.Load(tableName);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (table != <span style="color:#66d9ef">null</span>)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> GetGenericTableData(table, filters);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>After the message is published IoT will take care of the Data Collection Post.</p>
<p><br></br></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Connect IoT Debug Code Task</title><link>https://devblog.criticalmanufacturing.com/blog/20230906_iot_debug_code_task/</link><pubDate>Wed, 06 Sep 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230906_iot_debug_code_task/</guid><description>How To Debug Code Task</description><content:encoded><![CDATA[<p>This will be a how to, on debugging the code task.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview</li>
<li>How To</li>
</ol>
<h2 id="overview">Overview</h2>
<p>With the introduction of the code task, the need of customization has decreased substantially. Some of the complexity of customization has shifted to the code task. In software development, visibility and transparency on what is happening is essential, to better understand if the work fits the requirements and does not have undesirable side effects.</p>
<h2 id="how-to">How To</h2>
<p>Using the visual studio code as administrator we will debug the code task. In the code task add a hook <code>debugger;</code> this hook will provide a breakpoint for the appended execution of the visual studio code debugger to stop.</p>
<p>
<img src="/blogPosts/IoT/20230906_iot_debug_code_task/iot_debug_code_task_select_hook.png" alt="Adding Debugger Hook" />

</p>
<p>Start the <code>Automation Manager</code> and open visual studio code and select <a href="https://code.visualstudio.com/docs/getstarted/keybindings" target="_blank" rel="noopener"><code>F1</code></a>. After selecting <code>F1</code> a dropdown will appear, in this case we want to append to a node process, so select <code>Debug: Attach to Node Process</code>. It will show several node processes, the automation manager spawns at least 3 processes, one for the monitor, other for the driver and another for the controller. The code task is executed by the controller process, so that is the one we want to attach.</p>
<p>
<img src="/blogPosts/IoT/20230906_iot_debug_code_task/iot_debug_code_task_select.gif" alt="Attach Debugger" />

</p>
<p>Now, whenever the code task is invoked it will stop in VS Code.</p>
<p><br></br></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Connect IoT Deployment</title><link>https://devblog.criticalmanufacturing.com/blog/20230816_iot_deployment/</link><pubDate>Wed, 16 Aug 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230816_iot_deployment/</guid><description>Overview of the deployment options of Connect IoT</description><content:encoded><![CDATA[<p>This will be a small overview of the typical configurations that can be seen for running Connect IoT.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview</li>
<li>System Requirements</li>
<li>Using the MES GUI</li>
<li>Using the Setup</li>
<li>Running as a Console</li>
<li>Productive Deployment
<ol>
<li>Running as a Windows Service</li>
<li>Using the DevOps Center</li>
</ol>
</li>
</ol>
<h2 id="overview">Overview</h2>
<p>Connect IoT supports multiple architectures to best adapt to the integration scenario. The architecture of the application is centered on having a main or parent process, the <code>Automation Manager</code> and that process is responsible for spawning all the subprocesses of ConnectIoT.</p>
<p>Each process that constitutes Connect IoT is a nodejs process. The smallest amount of nodejs processes that Connect IoT requires to be fully functional is four. It requires the parent process <code>Automation Manager</code>, the <code>Automation Monitor</code>, then an <code>Automation Controller</code> and an <code>Automation Driver</code>.</p>
<p>Connect IoT also, requires a registry with the packages required to run. These packages may be in a directory or in an NPM registry, also they can be in a directory that the MES has access and then the <code>Automation Manager</code>, may request through REST requests the packages from the MES.</p>
<p>The <code>Automation Manager</code> also generates logging and is able to write to files process data, through a mechanism called persistency.</p>
<p>It is important to have a global idea of the architecture, so as to understand what may required in terms of deployment.</p>
<h2 id="system-requirements">System Requirements</h2>
<p>The system requirements vary according to what drivers your implementation uses. Some of them have particularities that are described in the help <a href="https://help.criticalmanufacturing.com/systemrequirements/connectrequirements/" target="_blank" rel="noopener"><code>Connect IoT Requirements</code></a>.</p>
<p>Regarding hardware it is somewhat hard to know before hand, as it depends on what you are using the manager for and the level of traffic and processing you are expecting. The hardware needs also vary by driver, some are more resource intensive than others. It is accepted that typically the bottleneck is RAM allocation <a href="https://help.criticalmanufacturing.com/systemrequirements/connectrequirements/#hardware-requirements" target="_blank" rel="noopener"><code>Hardware Requirements</code></a>.</p>
<h2 id="using-the-mes-gui">Using the MES GUI</h2>
<p>The first interaction most Connect IoT users has with the application is through the Automation Manager GUI, in that GUI it&rsquo;s possible to download a zip file with all that is needed to run the Automation Manager. The download will also generate a new authentication token for the user selected as integration user for the download.</p>
<p>
<img src="/blogPosts/IoT/20230816_iot_deployment/iot_deployment_manager_gui.png" alt="Automation Manager GUI" />

</p>
<p>It is important to note that the GUI allows you to customize for each particular manager the configuration that you want that manager to export when downloading. Also that configuration can be changed for all Automation Managers in the config <code>/Cmf/System/Configuration/ConnectIoT/ConfigurationTemplate/</code>, or in the specific Automation Manager. This may be very useful, to have pre configured all the configurations for what is relevant in your case.</p>
<p>The zip file that is generated contains the following:</p>
<ul>
<li>scripts (folder)</li>
<li>service (folder)</li>
<li>src (folder)</li>
<li>config.json</li>
<li>npm-shrinkwrap.json</li>
<li>package.json</li>
<li>README.md</li>
</ul>
<p>Some of these are more obvious than others.</p>
<p>Looking at the files:</p>
<p>Regarding versioning and metadata, that is assured by the npm-shrinkwrap.json and <strong>package.json</strong> 📄. The <strong>README.md</strong> 📄 contains the documentation on the automation manager entity The <code>config.json</code> 📄, controls all the key configuration for the <code>Automation Manager</code> and is a <em><strong>key part</strong></em> of the deployment process. All the fields of the config.json are described in the documentation under <a href="https://help.criticalmanufacturing.com/installationguide/post-installation/connect-iot/connectiotautomationmanagerconfig/" target="_blank" rel="noopener"><code>Help IoT Runtime Components Configuration</code></a></p>
<p>Looking at the folders:</p>
<p>The <strong>src</strong> 📁 folder is where the everything needed for the <code>Automation Manager</code> to run sits. In essence it is the source code of the application. The scripts and service folder are utilities that are included in the zip in order to be of easy use. The <strong>service</strong> 📁 has everything needed for the <code>Automation Manager</code> be installed as a Windows Service. The <strong>scripts</strong> 📁 has <code>StartConsole.bat</code> which enables to run the <code>Automation Manager</code> as a console application, it also has the <code>InstallService.ps1</code> this script allows you to install as a windows service.</p>
<h2 id="using-the-setup">Using the Setup</h2>
<p>The Critical Manufacturing Setup allows the deployment of an <code>Automation Manager</code> or several through a GUI. This method is useful if you want to deploy several managers, making the manual download through the GUI impractical and if you prefer to have a simple GUI abstraction.</p>
<p>In the <a href="https://help.criticalmanufacturing.com/installationguide/post-installation/connect-iot/connectiotinstallation/" target="_blank" rel="noopener"><code>Help Connect IoT Installation</code></a> all the setups and configurations are explained. It works by selecting to deploy the Cmf.ConnectIoT.Packages and then it allows for filling in the GUI all relevant fields for the config.json of the manager. It also supports the possibility of installing as a windows service.</p>
<p>
<img src="/blogPosts/IoT/20230816_iot_deployment/iot_deployment_setup.png" alt="Setup" />

</p>
<h2 id="running-as-a-console">Running as a Console</h2>
<p>This method is very useful, when <strong>developing or when troubleshooting an installation</strong>. It consists on running the process as a console, by using the <code>StartConsole.bat</code> script. When you run as a console, all the logs are aggregated as console output, so it may be easier to spot errors that are occurring.</p>
<h2 id="productive-deployment">Productive Deployment</h2>
<p>There are several different ways to have a productive deployment of the <code>Automation Manager</code>, but before starting with the differences, let&rsquo;s start with what is the same.</p>
<p>In a ❗High Availability❗ scenario, if a manager goes down, another must start to take it&rsquo;s place. It&rsquo;s important that whenever a new process starts, it doesn&rsquo;t start from scratch and that it inherits the context that the previous manager was working with. This is very impactful when thinking about implementations that are very reliant on a persistent layer for tracking or data collection. If manager 1 is tracking a material A and for some reason manager 1 goes down, it&rsquo;s important that the new manager 2 has a notion of material A. That is why typically, we suggest that the <em><strong>Storage/Persistency layer be stored in a shared folder accessible to all manager nodes</strong></em>, of course this depends on if the implementation uses the persistency layer for any logic.</p>
<p>It is also important to <strong>configure the logging appropriately</strong>, this is critical for very verbose drivers and implementations. You can configure retention times, rollovers, verbosity, etc <a href="https://help.criticalmanufacturing.com/installationguide/post-installation/connect-iot/connectiotautomationmanagerconfig/#logging-structure" target="_blank" rel="noopener"><code>Help Logging Configuration</code></a>. If it&rsquo;s important to keep the logs for a long time and a relevant amount, consider to also offload the logs to a shared folder, for risk mitigation.</p>
<p><strong>Certificates</strong>: It is required that a certificate is defined for the Automation Manager. The certificate must be placed (or path to the certificate) in an environment variable NODE_EXTRA_CA_CERTS <a href="https://help.criticalmanufacturing.com/installationguide/post-installation/connect-iot/connectiotinstallation/#unable-to-verify-the-first-certificate" target="_blank" rel="noopener"><code>Certificate Troubleshooting</code></a>. For <strong>development purposes</strong> the flag <code>NODE_TLS_REJECT_UNAUTHORIZED</code> can be set to 0 to use the certificate exactly as received. For more information and as a curiosity <a href="https://github.com/nodejs/node/pull/9139/files#diff-fbb424b0c726f25a6ab1100125b7e97a96142f900c0c328805905f39004e0e8e" target="_blank" rel="noopener"><code>Extra CA Certificates Nodejs</code></a> &amp; <a href="https://github.com/nodejs/node/issues/4175" target="_blank" rel="noopener"><code>Nodejs original issue</code></a></p>
<h3 id="running-as-a-windows-service">Running as a Windows Service</h3>
<p>This was traditionally the main approach of a production environment and is still very much a possibility today. Some drivers, like OIB and OPC-DA are windows based, one because it is built on NetFramework and the other because it uses the DCOM, which are windows exclusive. As explained before, both the download action in the MES GUI and the Setup provide easy ways to install the <code>Automation Manager</code> as a Windows Service <a href="https://github.com/winsw/winsw/tree/master" target="_blank" rel="noopener"><code>Library used for Creating nodejs windows services</code></a>. It is important to highlight that the user used to run the windows service is also very important, as the permissions, or lack thereof may cause side effects. Keep in mind also, that if the password has expiration this will impact the automation manager, that is running that user.</p>
<h4 id="high-availability-running-as-a-windows-service">High Availability Running as a Windows Service</h4>
<p>Achieving High Availability using windows services is possible using a microsoft technology the Windows Server Failover Cluster <a href="https://help.criticalmanufacturing.com/installationguide/planning-and-installation-guides/windows/windowsfailoverclustering/?h=failover" target="_blank" rel="noopener"><code>Help Windows Server Failover Clustering</code></a>. This allows for creating a cluster of different machines, where each machine will has installed the windows service we want to monitor. The Failover Cluster guarantees from the nodes, one and only one is always running. If one of the nodes goes down, meaning a windows service is shutdown, it will start in the other node a new windows service. The key and important part here is that node 1 and node 2 and node N all share the same accesses, configurations and so on, this enables it to be as smooth as possible when the failover occurs.</p>
<h3 id="using-the-devops-center">Using the DevOps Center</h3>
<p>Critical Manufacturing following the industry trend and simplifying the deployment process, created a portal to be able to remotely perform the deployments, this is the DevOps Center <a href="https://portal.criticalmanufacturing.com/Info/CustomerPortal.Support/devops_center" target="_blank" rel="noopener"><code>Help DevOps Center</code></a>. It also supports different orchestrators for deployment namely docker swarm and kubernetes and also different deployment targets <a href="https://portal.criticalmanufacturing.com/Info/CustomerPortal.Support/devops_center%3Edeployment_targets" target="_blank" rel="noopener"><code>Help Deployment Targets</code></a>.</p>
<p>
<img src="/blogPosts/IoT/20230816_iot_deployment/iot_deployment_devopscenter.png" alt="DevOps Center" />

</p>
<p>
<img src="/blogPosts/IoT/20230816_iot_deployment/iot_deployment_setup_manager.png" alt="DevOps Center Manager Installation" />

</p>
<h4 id="using-the-agent">Using the Agent</h4>
<p>Assuming you have an agent running in your machine, that agent will have the possibility to deploy automation managers. Doing that is a matter of, in your <a href="https://portal.criticalmanufacturing.com/Info/CustomerPortal.Support/devops_center%3Eguide%3Ecreate_infrastructure_agent" target="_blank" rel="noopener"><code>Infrastructure</code></a>, deploying new <code>Environments</code>, <a href="https://portal.criticalmanufacturing.com/Info/CustomerPortal.Support/devops_center%3Eguide%3Ecreate_mes_customer_environment" target="_blank" rel="noopener"><code>MES Example</code></a>, for each automation manager.</p>
<p>In order to deploy an <code>Automation Manager</code> select the Deployment Package, Automation Manager 10.1.0, or whichever version you need. Then select the target for the deployment. In the <code>General Data</code>, specify the Manager Id that you are deploying and the manager configuration related to system <a href="https://help.criticalmanufacturing.com/installationguide/post-installation/connect-iot/connectiotautomationmanagerconfig/#system-structure" target="_blank" rel="noopener"><code>Help System Configuration</code></a>. Regarding service resources, the automation manager will just run one instance of itself so just one replica. Volumes, will be where you specify where you will place the storage/persistency and the logs, also where the Connect IoT packages are accessible.</p>
<p>For certain drivers it is also important to create volumes, this is the case for example for all drivers that require file monitoring and processing. The DevOps Center allows you to set those mounts through the GUI as well. Currently, the only way to set open particular ports or ranges is by editing configuration yaml in the manager deployment.</p>
<p>Using the Agent, the agent will automatically deploy the stack.</p>
<h4 id="standalone-installation">Standalone Installation</h4>
<p>The Standalone Installation works in the same way as using the agent, but it will generate a zip file that can be manually executed. In other words it is the previous method but with a manual deployment.</p>
<p>The zip file that is generated depends on the <code>Target</code> selected, but will consist on scripts to deploy and remove the stack and then the needed configuration yaml files.</p>
<p><strong>Enabling Inbound Traffic</strong>: When using a driver that requires an external system to connect to it, instead of it connecting to an external system, a port will need to be opened for this access. Currently, the DevOps Center <strong>does not support</strong> setting it through the GUI. In order to do this, it will require a manual change in the configuration yaml, or in the system that is managing your stack, i.e portainer to open the port.</p>
<p><br></br></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Message Bus Communication Between Controllers</title><link>https://devblog.criticalmanufacturing.com/blog/20230704_iot_messagebus_mqtt/</link><pubDate>Tue, 04 Jul 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230704_iot_messagebus_mqtt/</guid><description>Small demo showcase of communication between controllers</description><content:encoded><![CDATA[<p>Today we will take a look at a use case for communication between Automation Controllers.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview</li>
<li>Use Case Overview</li>
<li>Use Case Implementation</li>
<li>Bonus</li>
</ol>
<p><strong>CAUTION</strong>: Just because you can, doesn&rsquo;t mean you should. Using the Message Bus as broadcaster between controllers can be very useful and powerful, but in a sense it&rsquo;s an anti-pattern. It bleeds over functionality between Controllers and bypasses the MES. Also, if not correctly setup it may be broadcasting payloads to a large number of active subscribers. The bigger the payload and the bigger the number of subscribers, the bigger the impact on the traffic in the network. You should always, only send exactly what you need and be as Peer to Peer as possible. Also, this is not transaction, this is an asynchronous method of communication. Even though we can send messages and wait for a reply.</p>
<h2 id="overview">Overview</h2>
<p>Connect IoT supports multiple architectures to best adapt to the integration scenario. When a machine has multiple interfaces, typically we can have one controller that orchestrates multiple drivers. The MES typically holds and persists the key process information and orchestrates and enriches the Connect IoT as needed, this way being able to save history, provide analytics and being more transparent to the end user.</p>
<p>Nevertheless, there are scenarios, where communication between controllers is needed, in a close to synchronous fashion. Where the push of information for a particular controller is not the machine nor the MES but the other controller. Critical Manufacturing provides a tool for just this type of broadcasts, the <a href="https://help.criticalmanufacturing.com/userguide/administration/message_bus/" target="_blank" rel="noopener">Message Bus</a>. This tool enables us to subscribe to topics and to broadcast messages. This scenario is also very helpful for interactions between the GUI or in pipelining <a href="https://help.criticalmanufacturing.com/userguide/business-data/automation-controller/tasks/core/task_systemevent/#example" target="_blank" rel="noopener">DEEs</a> for the Connect IoT.</p>
<p>This tutorial will focus only in a very simple example in MQTT. Other scenarios may be addressed in future posts.</p>
<h2 id="use-case-overview">Use Case Overview</h2>
<p>The use case for demonstrating this type of interaction, will be two lines. Where one controller is focusing on controlling one particular line. The Line 1 Controller will receive a PartsFinished and will then forward that information to the Line 2 Controller, which will iterate and set the value for the property partsToBeProduced. It will also perform a Line Clear that will clear the counter on Line 2.</p>
<p>This is a very simple interaction, but you can imagine the case, where you want segregation between two lines, and which are functionally different, but according to some logic you want the following line to be informed immediately on what hast been produced and is now to to be produced on the next line. Some parts of this, of course may make sense to represent in the MES and to not only broadcast a message but also, to persist in the MES this information.</p>
<h2 id="use-case-implementation">Use Case Implementation</h2>
<p>For this implementation we will use an MQTT broker <a href="https://mosquitto.org/" target="_blank" rel="noopener">Mosquitto</a>.</p>
<p>First, let&rsquo;s create the Automation Protocol, just keep the default values:</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_protocol.png" alt="MQTT Automation Protocol" />

</p>
<p>Now we need to create two different Automation Driver Definitions. For Line 1 we will have two events, one for the PartFinished and in Line 2 we will set a property value.</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_dd_properties.png" alt="Automation Driver Definition Line 1 Properties" />

</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_dd_events.png" alt="Automation Driver Definition Line 1 Events" />

</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_dd_eventproperties.png" alt="Automation Driver Definition Line 1 EventProperties" />

</p>
<p>For Line 2, let&rsquo;s define the property that we want to set:</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_dd_2_properties.png" alt="Automation Driver Definition Line 2 Properties" />

</p>
<h3 id="line-1-configuration">Line 1 Configuration</h3>
<p>Now we need to create two Automation Controllers, starting on the Line 1. Leave the Setup as default and create a new page <code>Part Finished</code>. For <code>Part Finished</code> we will receive an Event and broadcast a message to our new topic <code>Cmf.Line.PartsFinished</code>.</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_partsfinished.png" alt="Automation Controller Line 1 Parts Finished" />

</p>
<p>To try and make our solution a bit more generic, lets add context to our event and instead of just being a passthrough, lets also add the line as a context. This is done in the task <code>Expression Evaluator</code>.</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_partsfinished_message.png" alt="Automation Controller Line 1 Parts Finished Message" />

</p>
<h3 id="checking-message-bus-message-through-the-gui">Checking Message Bus Message through the GUI</h3>
<p>In the Administration/MessageBus section, we can generate and subscribe to Message Bus messages. If you want to subscribe all messages, use the special character <code>&gt;</code>. For our use case we can see the message being broadcast by adding a subscription to <code>Cmf.Line.PartsFinished</code>.</p>
<h3 id="line-2-configuration">Line 2 Configuration</h3>
<p>Leave the Setup as default and create a new page <code>Receive PartsFinished</code>. In this page we will subscribe to the message bus topic <code>Cmf.Line.PartsFinished</code> and we will retrieve the last value sent and set the property with the last value.</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_partstobeproduced.png" alt="Automation Controller Line 2 Receive PartsFinished" />

</p>
<p><strong>IMPORTANT</strong>: In this case we are using the context to filter messages that are from line 1 with a <code>Switch</code>, this allows to be more dynamic, on what messages to consume or not, this can also be done by generating unique topics that only the two peers know, allowing for a peer to peer communication. It&rsquo;s very important to try and minimize traffic in the network, so you want to as most as possible to only broadcast the message to who you want as a consumer.</p>
<h3 id="getting-it-to-work">Getting it to Work</h3>
<p>Create an Automation Manager and <code>Connect</code> the Automation Controllers. In my case, I have a manager called the MQTT_Manager.</p>
<p>First, let&rsquo;s start the broker with mosquitto.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>.\mosquitto.exe -v
</span></span></code></pre></div><p>We will need a publisher to broadcast the message of the parts finished and a subscriber to receive the parts to be produced.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>.\mosquitto_sub.exe -h localhost -t <span style="color:#e6db74">&#34;Line.2.PartsToBeProduced&#34;</span>
</span></span></code></pre></div><p>Start the Automation Manager, this will start both Controllers and Drivers.</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_manager.png" alt="Start Manager" />

</p>
<p>Let&rsquo;s start sending messages and see how the system behaves. For now, we will send two messages with 1.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>.\mosquitto_pub.exe -h localhost -t <span style="color:#e6db74">&#34;Line.1.PartFinished&#34;</span> -m <span style="color:#e6db74">&#34;1&#34;</span>
</span></span></code></pre></div><p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_partsfinished_two_message.png" alt="Manager Receive two Messages" />

</p>
<p>You can also see the value changing on the subscriber.</p>
<p>Right, now we already have a communication between both controllers through the Message Bus.</p>
<h2 id="bonus">Bonus</h2>
<p>Let&rsquo;s implement a Reset, so the use case would be to have a line clearance, where you want to cascade reset the counters.</p>
<p>In the driver definition create a new property for the topic <code>Line.1.ClearLine</code> and create a new event <code>OnLineClear</code>. For the Line 1 Controller create a new page <code>Line Clear</code>. In this page we will receive an event and activate a Message Bus Message for the topic Cmf.Line.Clear, in this case we will not add context.</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_lineClear.png" alt="Line 1 Clear" />

</p>
<p>In Line 2 we will now have a new page <code>Reset Counters</code> where we persist the value of the counter as 0 and set the property as <code>0</code>.</p>
<p>
<img src="/blogPosts/IoT/20230704_iot_messagebus_mqtt/iot_messagebus_mqtt_reset.png" alt="Line 2 Reset Counters" />

</p>
<p><br></br></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Document Generation</title><link>https://devblog.criticalmanufacturing.com/blog/20230621_dev_docs_plugin/</link><pubDate>Wed, 21 Jun 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230621_dev_docs_plugin/</guid><description>Explanation and demo of the plugin to generate documentation</description><content:encoded><![CDATA[<p>Let&rsquo;s try to generate documentation based on our own templates. This tutorial will focus on documentation for equipment characterization typically done by the colleagues of Deployment Services.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Overview and Motivation</li>
<li>Dependencies &amp; Installing</li>
<li>Generating Documentation</li>
<li>Creating your Templates</li>
</ol>
<h2 id="overview-and-motivation">Overview and Motivation</h2>
<p>One of the more difficult tasks a software developer has is producing documentation. Documentation is a very hard process, because it requires someone that understands profoundly the topics he is writing about, but at the same time is able to write in a way that is free from the assumptions and biases he has. Templating is good way to guide the writer into have a standardized approach to writing documentation and helping him with a set structure of topics he has to gather and fill.</p>
<p>The goal of this tool is to make it easier to write documentation and standardize the documentation through different use cases and in different teams. Also, the goal is to have it in such a way, where even people that are not developers are able to use and contribute.</p>
<h2 id="dependencies--installing">Dependencies &amp; Installing</h2>
<p>This tool requires the pre installation of:</p>
<ul>
<li><a href="https://www.atlassian.com/continuous-delivery/software-testing/types-of-software-testing" target="_blank" rel="noopener">Visual Studio Code</a></li>
<li><a href="https://criticalmanufacturing.github.io/cli/" target="_blank" rel="noopener">CMF CLI</a></li>
</ul>
<p>The tool is based on a visual studio code extension called <a href="https://github.com/Huuums/vscode-folder-templates" target="_blank" rel="noopener">VSCode Folder Templates</a>, also for convenience it installs an extension for mermaid <a href="https://github.com/mjbvz/vscode-markdown-mermaid" target="_blank" rel="noopener">VSCode Markdown Mermaid</a>. Mermaid is a tool to generate diagrams and more information on how to generate diagrams can be found here <a href="https://mermaid.js.org/" target="_blank" rel="noopener">Mermaid</a> and they also provide a very helpful <a href="https://mermaid.live/" target="_blank" rel="noopener">Mermaid Live Editor</a>.</p>
<p>To install the tool:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>npm install @criticalmanufacturing/doc-gen --registry=https<span style="color:#960050;background-color:#1e0010">:</span>//dev.criticalmanufacturing.io/repository/npm-public --global
</span></span></code></pre></div><p>The tool provides a set of predefined templates that you can import in your vscode, these templates can also receive inputs.</p>
<h2 id="generating-documentation">Generating Documentation</h2>
<p>Right now, the tool only has templates for IoT documentation. So the command available is to install IoT templates.</p>
<p>In order to install the IoT templates, you can run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>cmf doc-gen install iot
</span></span></code></pre></div><p>The command supports generating to a particular folder using <code>pathForTemplates</code>. The command also allows to add an exception in your <code>.gitignore</code> file, to not commit your templates with <code>--persistInProject</code>, if false it will add to the exclusions. It also has another option which is <code>--localInstall</code>, if this is passed as false it will install globally and be available every time you open vscode.</p>
<p>By default it adds the exclusion in your <code>.gitignore</code> file if it exists and creates locally.</p>
<p>The templates will be generated in a folder called <code>.fttemplates</code>:</p>
<p>
<img src="/blogPosts/IoT/20230621_dev_docs_plugin/dev_docs_plugin_iot_templates.png" alt="IoT Documentation Templates" />

</p>
<p>In order to use them, just right-click and select <code>Create New Templated Folder</code>, select the template and fill the inputs:</p>
<p>
<img src="/blogPosts/IoT/20230621_dev_docs_plugin/dev_docs_plugin_use.gif" alt="IoT Documentation Generation" />

</p>
<p>Ok, we have created our template and now we can edit it!!!</p>
<h2 id="creating-your-templates">Creating your Templates</h2>
<p>If you want to add your own templates, you can edit what is under <code>.fttemplates</code>, and edit existing or add new one&rsquo;s. If, for example in your project you want to add templates, make sure to commit the folder <code>.fttemplates</code>, so you don&rsquo;t lose them.</p>
<p>All the documentation on how to create templates is in <a href="https://github.com/Huuums/vscode-folder-templates" target="_blank" rel="noopener">VSCode Folder Templates</a> and also, you can check all our examples. Feel free to suggest new templates and to contribute. The source code for this plugin is currently under <a href="https://tfs-product.cmf.criticalmanufacturing.com/Products/Product/_git/DevOps?path=%2Fplugins%2Fcmf-doc-gen" target="_blank" rel="noopener">Devops doc-gen</a></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Send a Notification in Connect IoT</title><link>https://devblog.criticalmanufacturing.com/blog/20230606_iot_sending_notification/</link><pubDate>Tue, 06 Jun 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230606_iot_sending_notification/</guid><description>Small demo showcase of how to send a notification</description><content:encoded><![CDATA[<p>We are going to do a walkthrough of how we can send a notification using Connect IoT. This is just a suggestion of an implementation, every use case may have its particularities and variations.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Notification</li>
<li>Setting up the MES</li>
<li>Adding to our Workflow</li>
<li>Enriching our Notification</li>
</ol>
<h2 id="notification">Notification</h2>
<p>A <code>Notification</code> is an asynchronous way of broadcasting a message to all or some MES users. It allows to generate an alarm within the MES and without namely through sending emails. This is very useful to call the users to act upon a particular event. In this use case we will setup for a case, where we want to be notified whenever the system is disconnected.</p>
<p><strong>Info</strong>: Notifications are part of Alarm Management, which is a Critical Manufacturing optional module.</p>
<p><strong>Info</strong>: The user must have an Employee object associated in order to be able to use Notifications.</p>
<p><br></br></p>
<hr>
<h2 id="setting-up-the-mes">Setting up the MES</h2>
<p>In order to create a notification we must create a Rule that will invoke a DEE that will create a notification. This DEE may have inputs that will feed information to our Notification.</p>
<h3 id="creating-a-dee">Creating a DEE</h3>
<p>In this use case we will want to send a notification on disconnect. I will create a DEE called <code>CustomSendNotificationOnDisconnected</code> with classification <code>ConnectIoT</code>. I want my DEE to be flexible in creating a notification, this can be achieved with a table or in our case with configuration.</p>
<p>The first thing in our DEE is importing Foundation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>UseReference(<span style="color:#e6db74">&#34;Cmf.Foundation.BusinessObjects.dll&#34;</span>, <span style="color:#e6db74">&#34;Cmf.Foundation.BusinessObjects&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> serviceProvider = (IServiceProvider)Input[<span style="color:#e6db74">&#34;ServiceProvider&#34;</span>];
</span></span></code></pre></div><p>Now we can retrieve all our configs, in my case they live under /Notification/:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>Config.TryGetConfig(<span style="color:#e6db74">&#34;/Notification/Name&#34;</span>, <span style="color:#66d9ef">out</span> <span style="color:#66d9ef">var</span> notificationName);
</span></span><span style="display:flex;"><span>Config.TryGetConfig(<span style="color:#e6db74">&#34;/Notification/Description&#34;</span>, <span style="color:#66d9ef">out</span> <span style="color:#66d9ef">var</span> notificationDescription);
</span></span><span style="display:flex;"><span>Config.TryGetConfig(<span style="color:#e6db74">&#34;/Notification/Details&#34;</span>, <span style="color:#66d9ef">out</span> <span style="color:#66d9ef">var</span> notificationDetails);
</span></span><span style="display:flex;"><span>Config.TryGetConfig(<span style="color:#e6db74">&#34;/Notification/EmailDistributionList&#34;</span>, <span style="color:#66d9ef">out</span> <span style="color:#66d9ef">var</span> notificationEmailDistributionList);
</span></span><span style="display:flex;"><span>Config.TryGetConfig(<span style="color:#e6db74">&#34;/Notification/SendEmailToAssignedParty&#34;</span>, <span style="color:#66d9ef">out</span> <span style="color:#66d9ef">var</span> notificationSendEmailToAssignedParty);
</span></span><span style="display:flex;"><span>Config.TryGetConfig(<span style="color:#e6db74">&#34;/Notification/Severity&#34;</span>, <span style="color:#66d9ef">out</span> <span style="color:#66d9ef">var</span> notificationSeverity);
</span></span><span style="display:flex;"><span>Config.TryGetConfig(<span style="color:#e6db74">&#34;/Notification/Title&#34;</span>, <span style="color:#66d9ef">out</span> <span style="color:#66d9ef">var</span> notificationTitle);
</span></span><span style="display:flex;"><span>Config.TryGetConfig(<span style="color:#e6db74">&#34;/Notification/Type&#34;</span>, <span style="color:#66d9ef">out</span> <span style="color:#66d9ef">var</span> notificationType);
</span></span><span style="display:flex;"><span>Config.TryGetConfig(<span style="color:#e6db74">&#34;/Notification/NotificationAssignedToRole&#34;</span>, <span style="color:#66d9ef">out</span> <span style="color:#66d9ef">var</span> notificationAssignedToRole);
</span></span></code></pre></div><p>Feel free to create the configurations and populate with default values.</p>
<p>Now we can create our notification:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Cmf.Foundation.BusinessObjects.Abstractions.INotification notification = serviceProvider.GetService&lt;Cmf.Foundation.BusinessObjects.Abstractions.INotification&gt;();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>notification.Name = notificationName.Value + Guid.NewGuid().ToString();
</span></span><span style="display:flex;"><span>notification.Description = notificationDescription.Value <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>notification.Details = notificationDetails.Value <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>notification.EmailDistributionList = notificationEmailDistributionList.Value <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>notification.SendEmailToAssignedParty = ((<span style="color:#66d9ef">bool?</span>) notificationSendEmailToAssignedParty.Value) ?? <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>notification.Severity = notificationSeverity.Value <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>notification.Title = notificationTitle.Value <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>notification.Type = notificationType.Value <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (!<span style="color:#66d9ef">string</span>.IsNullOrEmpty(notificationAssignedToRole.Value <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>))
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	IRole role = serviceProvider.GetService&lt;IRole&gt;();
</span></span><span style="display:flex;"><span>	role.Load(notificationAssignedToRole.Value <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>);
</span></span><span style="display:flex;"><span>	notification.AssignedToRole = role;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>notification.Create();
</span></span></code></pre></div><p>For now we are just using the configurations to provide information to our notification.</p>
<p>Now we can execute our DEE (select <code>Execute</code> button) and see the notification being created.</p>
<p>
<img src="/blogPosts/IoT/20230606_iot_sending_notification/iot_sending_notification_dee_createnotification.png" alt="Execute DEE Create Notification" />

</p>
<h3 id="creating-a-rule">Creating a Rule</h3>
<p>The DEE is working, we need the action that will wrap it and be used to trigger it. The action must be of <code>Scope</code> ConnectIoT and of course it needs to select the DEE that was created.</p>
<p>
<img src="/blogPosts/IoT/20230606_iot_sending_notification/iot_sending_notification_rule.png" alt="Create Rule" />

</p>
<h2 id="adding-to-our-workflow">Adding to our Workflow</h2>
<p>We have a Rule and this rule can be invoked in the Connect IoT Workflow. In our example, we want to create a notification if we disconnect. The communication state is given by the task <code>On Equipment Setup</code> output OnCommunicationChange. This output will always emit the new communication state. In order to address, this we will create a switch for the state machine of the communication state.</p>
<p>
<img src="/blogPosts/IoT/20230606_iot_sending_notification/iot_sending_notification_switch_communication_state.png" alt="Create Switch" />

</p>
<p>After having our switch we can use the Disconnected output to feed our Rule. In order to invoke rules in Connect IoT we use the task <code>Execute Action</code>. In this task, select the Rule that we&rsquo;ve created.</p>
<p><strong>Info</strong>: Only Rules created under the scope ConnectIoT will show in the task to be selected.</p>
<p>
<img src="/blogPosts/IoT/20230606_iot_sending_notification/iot_sending_notification_execute_action.png" alt="Create Execute Action" />

</p>
<p>Connecting everything:</p>
<p>
<img src="/blogPosts/IoT/20230606_iot_sending_notification/iot_sending_notification_setup.png" alt="Setup Workflow" />

</p>
<h3 id="running-the-automation-manager">Running the Automation Manager</h3>
<p>In order to test this implementation, simply start the Automation Manager and then Stop it. This will trigger a Disconnected communication state that will trigger the notification.</p>
<p>
<img src="/blogPosts/IoT/20230606_iot_sending_notification/iot_sending_notification_manager_disconnected.png" alt="Running Manager" />

</p>
<p>You can see the notification is called and it will show as the one generated when we executed the DEE.</p>
<h2 id="enriching-our-notification">Enriching our Notification</h2>
<p>In our previous use case, even with configurations, we had a very static definition of our notification. Let&rsquo;s then save an error message in the <code>Notification</code> details. This could come from the Error output of a task, but in this case let&rsquo;s just create a constant message saying <code>Controller Disconnected</code>.</p>
<p>In the Execute Action task create an Input ErrorMessage and as default value <code>Controller Disconnected</code>. In the DEE we will need to change it to receive an external input called ErrorMessage and change the details of the notification to log our error message.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-csharp" data-lang="csharp"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> errorMessage = Input[<span style="color:#e6db74">&#34;ErrorMessage&#34;</span>] <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>notification.Details = errorMessage;
</span></span></code></pre></div><p>Save the DEE as a new effective version.</p>
<p>Now, repeating the process of starting and stopping the automation a new notification is create and has as details our expected message.</p>
<p>
<img src="/blogPosts/IoT/20230606_iot_sending_notification/iot_sending_notification_dee_createnotification_input.png" alt="Create Notification" />

</p>
<p>This finishes our post, as you can see the mechanisms of communication between the Connect IoT and the MES are very powerful and can provide a lot of synergies.</p>
<p><br></br></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>IoT Tasks Unit Tests</title><link>https://devblog.criticalmanufacturing.com/blog/20230404_iot_unit_tests/</link><pubDate>Tue, 04 Apr 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230404_iot_unit_tests/</guid><description>Explanation of how unit tests work for IoT Tasks</description><content:encoded><![CDATA[<p>We are going to do a walkthrough of how the unit tests are built and how you can easily guard and tests your customization using unit tests.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>What is a Unit Test?</li>
<li>What do we use Unit Testing for?</li>
<li>Creating a Unit Test</li>
<li>Creating a Unit Test for Tasks</li>
<li>Bonus Content - Coverage</li>
</ol>
<h2 id="what-is-a-unit-test">What is a Unit Test?</h2>
<p>Unit testing is a type of testing which we try to isolate the components or object of testing. In this type of test we are testing, not the interaction with multiple components, but the most atomic set of functional code. For example, testing a workflow execution and seeing if a request for MES is at least an interaction between several tasks (which are blocks of code components) and an external system, like an MES or even a persistence layer.</p>
<p>In this post I want to talk about testing an IoT Task. An IoT Task is something that is mostly contained, it may or may not have settings, inputs and outputs and it may have colateral effects. Typically, a task behaves mostly as a function with inputs and outputs and some configurations, so it&rsquo;s a prime target for Unit Testing.</p>
<p>The main advantage of unit testing are the consistency checks, speed of execution and &ldquo;noise reduction&rdquo;. A unit test will maintain that give conditions A a task will always give output B and will not be burden by externalities, if for some reason a change is introduced in the task and now A is C, the consistency is no longer and the test will fail. They are very fast as they are only code executions or function executions, so its small scope and focus ensure atomic executions.</p>
<p>As mentioned previously this allows you to concentrate in testing the component without having to worry about interactions with other systems or components, of course they <strong>don&rsquo;t replace End to End tests or integration tests</strong> as those interactions are outside the scope of this type of test.</p>
<p>Another advantage is that they can be run at commit time and not at build time. Being very fast, they can be run using git hooking tools like <a href="https://github.com/typicode/husky" target="_blank" rel="noopener">husky</a>. This can be a big advantage, because if the tests are going to fail we want them to fail as soon as possible and not wait for a cumbersome build to fail. So we can save time and resources.</p>
<p>I hope it is clear what are the advantages and disadvantages. For more information about testing I would check <a href="https://www.geeksforgeeks.org/types-software-testing" target="_blank" rel="noopener">GeeksForGeeks</a> or <a href="https://www.atlassian.com/continuous-delivery/software-testing/types-of-software-testing" target="_blank" rel="noopener">Atlassian</a>.</p>
<h2 id="what-do-we-use-unit-testing-for">What do we use Unit Testing for?</h2>
<p>The IoT Tasks scaffolding already gives everything you need to start testing. It will generate a <code>/test/</code> folder in your taskPackage, also in also creates the package.json with all the devDependencies you need to run the tests.</p>
<p>It will mainly use two tools <a href="https://mochajs.org/" target="_blank" rel="noopener">mocha</a> and <a href="https://www.chaijs.com/" target="_blank" rel="noopener">chai</a>.</p>
<p><strong>Mocha</strong> is a test framework for javascript tests. The tests will have a test suite under <code>describe</code>, they can have some steps before executing in a <code>beforeEach</code>, before testing is where the containers for the normal components should be overriden by the test components, more on this further ahead. A test is defined by <code>it</code> followed by what it should do. For example, <code>it('should return a string with Hello World', ... )</code>.</p>
<p>The addition of <strong>chai</strong> is to further strenghten our assertion to be as <em>word like</em> as possible. For example, using chai we can write our test in way that is much clearer. We are not asserting that result <code>result === &quot;Hello World&quot;</code>, we are expecting result to be equal to Hello World <code>expect(result).to.equal('Hello World');</code> which is much more transparent.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#39;should return a string&#39;</span>, <span style="color:#66d9ef">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        (...)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">result</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#39;Hello World&#39;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">result</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">be</span>.<span style="color:#a6e22e">a</span>(<span style="color:#e6db74">&#39;string&#39;</span>);
</span></span><span style="display:flex;"><span>    });
</span></span></code></pre></div><p>There is more variation, I suggest reading the documentation on both these tools to unlock their full potential.</p>
<h2 id="creating-a-unit-test">Creating a Unit Test</h2>
<p>Let&rsquo;s start with the simplest and most atomic business logic unit of IoT, the <strong>converter</strong>. I am using an example of a converter that was generated but no tests have been created yet, so it&rsquo;s a good opportunity to use a real use-case. The template has generated something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#e6db74">&#34;reflect-metadata&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Task</span>, <span style="color:#a6e22e">System</span>, <span style="color:#a6e22e">TYPES</span>, <span style="color:#a6e22e">DI</span>, <span style="color:#a6e22e">Converter</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@criticalmanufacturing/connect-iot-controller-engine&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">EngineTestSuite</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@criticalmanufacturing/connect-iot-controller-engine/test&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">as</span> <span style="color:#a6e22e">chai</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;chai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">CustomArrayDeltaCalculationConverter</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;../../../../src/converters/customArrayDeltaCalculation/customArrayDeltaCalculation.converter&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">describe</span>(<span style="color:#e6db74">&#34;Array Delta Calculation converter&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">converter</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Converter</span>.<span style="color:#a6e22e">ConverterContainer</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">beforeEach</span>(<span style="color:#66d9ef">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">converter</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">EngineTestSuite</span>.<span style="color:#a6e22e">createConverter</span>({
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">class</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">CustomArrayDeltaCalculationConverter</span>
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#34;should convert&#34;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">done</span>) =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">/* Example int to string*/</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">result</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">converter</span>.<span style="color:#a6e22e">execute</span>(<span style="color:#ae81ff">123</span>, {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">parameter</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;something&#34;</span>
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">result</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;123&#34;</span>);
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">done</span>();
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Taking a glance at this we can see somethings that are familiar to us. We have the import of chai, so we know we can use our assertion methods, also we are importing the controller engine and its test package, finally we have our own custom converter. In the body we can see our <code>describe</code> from mocha, so this is where our test suite begins, we see it&rsquo;s already creating our converter container, and we already have a dummy test showing how we can invoke our converter.</p>
<p>In order to start building tests let&rsquo;s see what our converter does. We can see in the comment it receives an array, a type and extracts the value from that position and typecasts it. This is common, converter tasks shouldn&rsquo;t be bloated in terms of functionality, they should be straightforward.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#75715e">/**
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> * Extracts a value from a position of an array and type casts it
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> * @param position position that we want to extract from array
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> * @param type type of the value to be extracted from the position
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">transform</span>(<span style="color:#a6e22e">value</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">object</span>, <span style="color:#a6e22e">parameters</span><span style="color:#f92672">:</span> { [<span style="color:#a6e22e">key</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>]<span style="color:#f92672">:</span> <span style="color:#a6e22e">any</span>; })<span style="color:#f92672">:</span> <span style="color:#a6e22e">any</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span>(<span style="color:#a6e22e">value</span> <span style="color:#66d9ef">instanceof</span> Array)) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#34;Given input value is not an instance of an Array&#34;</span>);
</span></span><span style="display:flex;"><span>    }  <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Utilities</span>.<span style="color:#a6e22e">convertValueToType</span>(<span style="color:#a6e22e">value</span>[<span style="color:#a6e22e">parameters</span>[<span style="color:#e6db74">&#34;position&#34;</span>]], <span style="color:#a6e22e">parameters</span>[<span style="color:#e6db74">&#34;type&#34;</span>], <span style="color:#66d9ef">undefined</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><br></br></p>
<hr>
<p><br></br></p>
<p>Before starting to code, let&rsquo;s add the vscode launch setting in order to be able to debug our tests. So in our case we have a custom task package, generically called utilities, we are saying that we want to run from the package, so our workspaceRoot is where our console will launch and we want to run mocha and our tests are under test and end in .test.js.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;Run tests on controller-engine-custom-utilities-tasks&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;node&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;request&#34;</span>: <span style="color:#e6db74">&#34;launch&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;program&#34;</span>: <span style="color:#e6db74">&#34;${workspaceRoot}/node_modules/mocha/bin/_mocha&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;stopOnEntry&#34;</span>: <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;args&#34;</span>: [<span style="color:#e6db74">&#34;test/**/*.test.js&#34;</span>, <span style="color:#e6db74">&#34;test/*.test.js&#34;</span>, <span style="color:#e6db74">&#34;--no-timeouts&#34;</span>],
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;cwd&#34;</span>: <span style="color:#e6db74">&#34;${workspaceRoot}&#34;</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;runtimeExecutable&#34;</span>: <span style="color:#66d9ef">null</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;sourceMaps&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>   <span style="color:#f92672">&#34;outputCapture&#34;</span>: <span style="color:#e6db74">&#34;std&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In VSCode we can now <code>Run and Debug</code> and select our test and add breakpoints and debug our test. A useful command that comes out of the box from the scaffolding is:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm run watchTest
</span></span></code></pre></div><p>This command will always compile the tests when a change is detected. There is a watchPackage for the src and it behaves the same and also a watch which makes sure to compile both are always keep running the unit tests.</p>
<p><br></br></p>
<hr>
<p><br></br></p>
<p>Our first test will be to make sure that if we receive an invalid input, in other words not an array, we want to receive an error. We send an integer value and catch the exception and compare the message to the expected value. This way we cover that our converter will only work when expected.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#34;should fail if value is not an array&#34;</span>, <span style="color:#66d9ef">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">converter</span>.<span style="color:#a6e22e">execute</span>(<span style="color:#ae81ff">123</span>, {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">type</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">TaskValueType</span>.<span style="color:#a6e22e">Integer</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">position</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>        }).<span style="color:#66d9ef">catch</span>(<span style="color:#a6e22e">error</span> =&gt; <span style="color:#a6e22e">error</span>.<span style="color:#a6e22e">message</span>)
</span></span><span style="display:flex;"><span>    ).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;Given input value is not an instance of an Array&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Awesome, very simple, now let&rsquo;s do a happy test scenario, we will give it an array with two positions, select the second and cast it to number.</p>
<p><strong><em>NOTE:</em></strong> Sometimes it&rsquo;s useful to add it.only, to our test, this will make it so it only runs that particular test.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#34;should succeed in retrieving second array position and converting it to number&#34;</span>, <span style="color:#66d9ef">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">converter</span>.<span style="color:#a6e22e">execute</span>([<span style="color:#e6db74">&#34;test&#34;</span>, <span style="color:#ae81ff">1</span>], {
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">type</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">TaskValueType</span>.<span style="color:#a6e22e">Integer</span>,
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">position</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>                    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">result</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">be</span>.<span style="color:#a6e22e">a</span>(<span style="color:#e6db74">&#39;number&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">result</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>In this test, I splitted the execution to a property to be more legible. The test validates the output is a number and its value is <code>1</code>. Pretty simple, right! 👏</p>
<h2 id="creating-a-unit-test-for-tasks">Creating a Unit Test for Tasks</h2>
<p>As you can imagine, tasks have more variability, but the good news is that the jist is more or less the same. So let´s give it a try. Again, there&rsquo;s a lot that already comes in the scaffolding, so let&rsquo;s take a look at what comes in the scaffolding, in this case this was generated for a task called <code>CustomResolveMetadataFeature</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#e6db74">&#34;reflect-metadata&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Task</span>, <span style="color:#a6e22e">System</span>, <span style="color:#a6e22e">TYPES</span>, <span style="color:#a6e22e">DI</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@criticalmanufacturing/connect-iot-controller-engine&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">EngineTestSuite</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@criticalmanufacturing/connect-iot-controller-engine/test&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// import { DataStoreMock } from &#34;@criticalmanufacturing/connect-iot-controller-engine/test/mocks/dataStore.mock&#34;;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">as</span> <span style="color:#a6e22e">chai</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;chai&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">CustomResolveMetadataFeatureTask</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">CustomResolveMetadataFeatureSettings</span>
</span></span><span style="display:flex;"><span>} <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;../../../../src/tasks/customResolveMetadataFeature/customResolveMetadataFeature.task&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">CustomResolveMetadataFeatureTaskModule</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;../../../../src/tasks/customResolveMetadataFeature/index&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">describe</span>(<span style="color:#e6db74">&#34;CustomResolveMetadataFeature Task tests&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Optional: See container handling under customResolveMetadataFeatureTestFactory
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// let dataStoreMock: DataStoreMock;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">beforeEach</span>(() =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// dataStoreMock = new DataStoreMock();
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">customResolveMetadataFeatureTestFactory</span> <span style="color:#f92672">=</span> (  <span style="color:#a6e22e">settings</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">CustomResolveMetadataFeatureSettings</span> <span style="color:#f92672">|</span> <span style="color:#66d9ef">undefined</span>,
</span></span><span style="display:flex;"><span>                                            <span style="color:#a6e22e">trigger</span><span style="color:#f92672">:</span> Function,
</span></span><span style="display:flex;"><span>                                            <span style="color:#a6e22e">validate</span><span style="color:#f92672">:</span> Function)<span style="color:#f92672">:</span> <span style="color:#66d9ef">void</span> =&gt; {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">taskDefinition</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">class</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">CustomResolveMetadataFeatureTaskModule</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;0&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">settings</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">settings</span> <span style="color:#f92672">||</span> <span style="color:#f92672">&lt;</span><span style="color:#a6e22e">CustomResolveMetadataFeatureSettings</span><span style="color:#f92672">&gt;</span>{
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">message</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">EngineTestSuite</span>.<span style="color:#a6e22e">createTasks</span>([
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">taskDefinition</span>,
</span></span><span style="display:flex;"><span>            {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;1&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">class</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Task</span>({
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;mockTask&#34;</span>
</span></span><span style="display:flex;"><span>                })(<span style="color:#66d9ef">class</span> <span style="color:#a6e22e">MockTask</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">TaskInstance</span> {
</span></span><span style="display:flex;"><span>                    [<span style="color:#a6e22e">key</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>]<span style="color:#f92672">:</span> <span style="color:#a6e22e">any</span>;
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">_outputs</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;&gt;</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;&gt;</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">async</span> <span style="color:#a6e22e">onBeforeInit</span>()<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#66d9ef">void</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#34;activate&#34;</span>] <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;</span>();
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">_outputs</span>.<span style="color:#a6e22e">set</span>(<span style="color:#e6db74">&#34;activate&#34;</span>, <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#34;activate&#34;</span>]);
</span></span><span style="display:flex;"><span>                        <span style="color:#75715e">// Create other custom outputs (for the Mock task) here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// Trigger the test
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                    <span style="color:#66d9ef">async</span> <span style="color:#a6e22e">onInit</span>()<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#66d9ef">void</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">trigger</span>(<span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">_outputs</span>);
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// Validate the results
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                    <span style="color:#66d9ef">async</span> <span style="color:#a6e22e">onChanges</span>(<span style="color:#a6e22e">changes</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Changes</span>)<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#66d9ef">void</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">validate</span>(<span style="color:#a6e22e">changes</span>);
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                })
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        ], [
</span></span><span style="display:flex;"><span>            { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;1&#34;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`activate`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;0&#34;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;activate&#34;</span>, },
</span></span><span style="display:flex;"><span>            { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;0&#34;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`success`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;1&#34;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;success&#34;</span>, },
</span></span><span style="display:flex;"><span>            { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;0&#34;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`error`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;1&#34;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;error&#34;</span>, },
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Add more links needed here...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        ],
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">undefined</span>,
</span></span><span style="display:flex;"><span>        (<span style="color:#a6e22e">containerId</span>) =&gt; {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Change what you need in the container
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#75715e">// Example:
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#75715e">// containerId.unbind(TYPES.System.PersistedDataStore);
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#75715e">// containerId.bind(TYPES.System.PersistedDataStore).toConstantValue(dataStoreMock);
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        });
</span></span><span style="display:flex;"><span>    };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/**
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * Instructions about the tests
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * It is assumed that there are two tasks:
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *    0 - CustomResolveMetadataFeature Task
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *    1 - Mockup task
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * All Outputs of Mock task are connected to the inputs of the CustomResolveMetadataFeature task
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * All Outputs of CustomResolveMetadataFeature Task are connected to the Mock task inputs
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * You, as the tester developer, will trigger the outputs necessary for the CustomResolveMetadataFeature to be activated
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * and check the changes to see if the CustomResolveMetadataFeature task sent you the correct values
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     * Note: This is just an example about how to unit test the task. Not mandatory to use this method!
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     */</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#34;should get success when activated&#34;</span>, (<span style="color:#a6e22e">done</span>) =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">customResolveMetadataFeatureTestFactory</span>(<span style="color:#66d9ef">undefined</span>,
</span></span><span style="display:flex;"><span>            (<span style="color:#a6e22e">outputs</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;&gt;</span>) =&gt; {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Trigger an output
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#34;activate&#34;</span>).<span style="color:#a6e22e">emit</span>(<span style="color:#66d9ef">true</span>);
</span></span><span style="display:flex;"><span>            }, (<span style="color:#a6e22e">changes</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Changes</span>) =&gt; {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Validate the input
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">changes</span>[<span style="color:#e6db74">&#34;success&#34;</span>].<span style="color:#a6e22e">currentValue</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#66d9ef">true</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Report the test as a success
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">done</span>();
</span></span><span style="display:flex;"><span>            });
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Pretty intimidating, right? But already we see things that are familiar to us. We see a <code>describe</code> for our test suite, we see a <code>beforeEach</code> and an <code>it</code> for our test run and also an <code>EngineTestSuite.createTasks</code>, similar to the EngineTestSuite.createConverter we saw and used above. There is a lot of information that is comented and that can throw you in a loop, but let&rsquo;s try and see if we can untangle it.</p>
<p><strong>Philosophy</strong>: The philosophy behind this, is we kind of want to have our own workflow, but just for this task. We are able to inject it with inputs and then receive as inputs the output of our task.There&rsquo;s a common practice to build a diagram to try and explain how the test will work:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>    <span style="color:#75715e">/*
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *               +----------------------------------------+       +-----------------------------------------------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *               |            ==mockTask (0)==            |       |         ==CustomResolveMetadataFeature (1)==              |
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *               |                                        |       |                                                           |
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *    (*1) ----&gt; | () barcodeOut             barcodeIn () | ----&gt; | () barcodeIn                            barcodeOut     () | ----&gt; (*1)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *    (*2) ----&gt; | () entityOut               entityIn () | ----&gt; | () entityIn                             entityOut      () | ----&gt; (*2)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *    (*3) ----&gt; | () activateOut            eventType () | ----&gt; | () eventType                            activateOut    () | ----&gt; (*3)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *    (*4) ----&gt; | () notActivateOut            inputs () | ----&gt; |                                         notActivateOut () | ----&gt; (*4)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *               |----------------------------------------|       |-----------------------------------------------------------|
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *    (*5) ----&gt; | () success                 activate () | ----&gt; | () activate                                    success () | ----&gt; (*5)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *    (*6) ----&gt; | () error                               |       |                                                  error () | ----&gt; (*6)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     *               +----------------------------------------+       +-----------------------------------------------------------+
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">     */</span>
</span></span></code></pre></div><p>The diagram makes clear what are the inputs and outputs of our task. I chose a somewhat complex task on purpose, so we could see some more challenges.</p>
<p>Right off the bat we see that our focus, before anything else should be on the createTasks, and trying to setup what will be our task execution Mock.</p>
<p>Ok, let&rsquo;s fill in our settings with the settings of the task and some dummy values:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">taskDefinition</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">class</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">CustomResolveMetadataFeatureTaskModule</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;1&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">settings</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">settings</span> <span style="color:#f92672">||</span> <span style="color:#f92672">&lt;</span><span style="color:#a6e22e">CustomResolveMetadataFeatureSettings</span><span style="color:#f92672">&gt;</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">functionalityName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;functionalityName&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">enableFunctionalityName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;IsEnabledFunctionalityName&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">configurationTable</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;TestSmartTable&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>};  
</span></span></code></pre></div><p>Next off, we are injecting the outputs of our mock task to our actual task, we need to add those to the <code>EngineTestSuite.createTasks</code> in the <code>onBeforeInit</code> to make sure they are instantianted. We can leave the <code>onInit</code> and <code>onChanges</code> as is.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#a6e22e">onBeforeInit</span>()<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#66d9ef">void</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// barcodeIn
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#39;barcodeIn&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">string</span><span style="color:#f92672">&gt;</span>();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">set</span>(<span style="color:#e6db74">&#39;barcodeIn&#39;</span>, <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#39;barcodeIn&#39;</span>]);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// entityIn
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#39;entityIn&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">EntityType</span><span style="color:#f92672">&gt;</span>();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">set</span>(<span style="color:#e6db74">&#39;entityIn&#39;</span>, <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#39;entityIn&#39;</span>]);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// eventType
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#39;eventType&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">string</span><span style="color:#f92672">&gt;</span>();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">set</span>(<span style="color:#e6db74">&#39;eventType&#39;</span>, <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#39;eventType&#39;</span>]);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// activate
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#39;activate&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#66d9ef">boolean</span><span style="color:#f92672">&gt;</span>();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">set</span>(<span style="color:#e6db74">&#39;activate&#39;</span>, <span style="color:#66d9ef">this</span>[<span style="color:#e6db74">&#39;activate&#39;</span>]);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Adding links, the diagram is really helpful to understand this. We want to connect the mock task (0) to our task (1).</p>
<p><strong>NOTE</strong>: Outputs and inputs have the same names, just for convenience but you can give it any name you want.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Inputs of task -&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`barcodeIn`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;barcodeIn&#39;</span> },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`entityIn`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;entityIn&#39;</span> },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`eventType`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;eventType&#39;</span> },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`activate`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;activate&#39;</span> },
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Outputs of task -&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`entityOut`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;entityOut&#39;</span> },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`barcodeOut`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;barcodeOut&#39;</span> },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`notActivateOut`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;notActivateOut&#39;</span> },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`activateOut`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;activateOut&#39;</span> },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`success`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;success&#39;</span> },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">sourceId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1&#39;</span>, <span style="color:#a6e22e">outputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`error`</span>, <span style="color:#a6e22e">targetId</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">inputName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;error&#39;</span> }
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>We are not overriding the driver proxy, so you can leave it undefined. Now the interesting bit, in this task it&rsquo;s going to resolve a smart table and also persit data in the Connect IoT Persistency. So we need to inject our mock container for <em>Datastore</em> and for <em>System.Proxy</em>.</p>
<p>For <em>Datastore</em>, because this is very common the template helps you along. In the <code>beforeEach</code> uncomment the commented code, also do the same in the import:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">DataStoreMock</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#34;@criticalmanufacturing/connect-iot-controller-engine/test/mocks/dataStore.mock&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">let</span> <span style="color:#a6e22e">dataStoreMock</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">DataStoreMock</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">beforeEach</span>(() =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">dataStoreMock</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">DataStoreMock</span>();
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>In our <code>EngineTestSuite.createTasks</code> in the <code>(containerId) =&gt; {</code> section we can bind our mock containers. For our <em>Datastore</em> we just unbind and bind ours, declared above, for the <em>System.Proxy</em> we need to declare what we are going to reply when we get the resolve table smart table request.</p>
<p>Binding the <em>Datastore</em> (I changed containerId to container):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>(<span style="color:#a6e22e">container</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">unbind</span>(<span style="color:#a6e22e">TYPES</span>.<span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">PersistedDataStore</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">bind</span>(<span style="color:#a6e22e">TYPES</span>.<span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">PersistedDataStore</span>).<span style="color:#a6e22e">toConstantValue</span>(<span style="color:#a6e22e">dataStoreMock</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In creating our new container to intercept the request, we need to specify it implements<em>System.Proxy</em>, and it will require you to implement a set of functions. We will implemnt them and say if they are called throw an exception.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>(<span style="color:#a6e22e">container</span>) =&gt; {
</span></span><span style="display:flex;"><span>    (...)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">MockSystemAPI</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">SystemProxy</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">getMetadata</span>()<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">Foundation</span>.<span style="color:#a6e22e">BusinessObjects</span>.<span style="color:#a6e22e">AutomationController</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unexpected call&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">subscribeActionGroup</span>(<span style="color:#a6e22e">actionGroup</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Foundation</span>.<span style="color:#a6e22e">Common</span>.<span style="color:#a6e22e">DynamicExecutionEngine</span>.<span style="color:#a6e22e">ActionGroup</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">string</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">callback</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">SystemEventCallback</span>)<span style="color:#f92672">:</span> <span style="color:#66d9ef">void</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unexpected call&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">unsubscribeActionGroup</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">actionGroup</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Foundation</span>.<span style="color:#a6e22e">Common</span>.<span style="color:#a6e22e">DynamicExecutionEngine</span>.<span style="color:#a6e22e">ActionGroup</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">string</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">callback</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">SystemEventCallback</span>)<span style="color:#f92672">:</span> <span style="color:#66d9ef">void</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unexpected call&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">executeQuery</span>(<span style="color:#a6e22e">queryObject</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">QueryObject</span>.<span style="color:#a6e22e">QueryObject</span>, <span style="color:#a6e22e">parameterCollection</span><span style="color:#f92672">?:</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">QueryObject</span>.<span style="color:#a6e22e">QueryParameterCollection</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">settings</span><span style="color:#f92672">?:</span> <span style="color:#a6e22e">Utilities</span>.<span style="color:#a6e22e">SystemApiUtilsSettings</span>)<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">LBOS</span>.<span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">Data</span>.<span style="color:#a6e22e">DataSet</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unexpected call&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">getObjectById</span>(<span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">type</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">levelsToLoad</span><span style="color:#f92672">?:</span> <span style="color:#a6e22e">number</span>, <span style="color:#a6e22e">typeIsTypeId</span><span style="color:#f92672">?:</span> <span style="color:#66d9ef">boolean</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">settings</span><span style="color:#f92672">?:</span> <span style="color:#a6e22e">Utilities</span>.<span style="color:#a6e22e">SystemApiUtilsSettings</span>)<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unexpected call&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">getObjectByName</span>(<span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">type</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">levelsToLoad</span><span style="color:#f92672">?:</span> <span style="color:#a6e22e">number</span>, <span style="color:#a6e22e">typeIsTypeId</span><span style="color:#f92672">?:</span> <span style="color:#66d9ef">boolean</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">settings</span><span style="color:#f92672">?:</span> <span style="color:#a6e22e">Utilities</span>.<span style="color:#a6e22e">SystemApiUtilsSettings</span>)<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unexpected call&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">loadAttributes</span>(<span style="color:#a6e22e">entity</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">any</span>, <span style="color:#a6e22e">specificAttributes</span><span style="color:#f92672">?:</span> <span style="color:#a6e22e">string</span>[], <span style="color:#a6e22e">settings</span><span style="color:#f92672">?:</span> <span style="color:#a6e22e">Utilities</span>.<span style="color:#a6e22e">SystemApiUtilsSettings</span>)<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unexpected call&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">adjustState</span>(<span style="color:#a6e22e">entity</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">any</span>, <span style="color:#a6e22e">newState</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">settings</span><span style="color:#f92672">?:</span> <span style="color:#a6e22e">Utilities</span>.<span style="color:#a6e22e">SystemApiUtilsSettings</span>)<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Error(<span style="color:#e6db74">&#39;Unexpected call&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now we implement our call:</p>
<p><strong>NOTE</strong>: There may be imports you need to add (i.e import <code>Foundation = System.LBOS.Cmf.Foundation;</code>)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>(<span style="color:#a6e22e">container</span>) =&gt; {
</span></span><span style="display:flex;"><span>    (...)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">MockSystemAPI</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">SystemProxy</span> {
</span></span><span style="display:flex;"><span>    (...)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#a6e22e">call</span>(<span style="color:#a6e22e">input</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">LBOS</span>.<span style="color:#a6e22e">Cmf</span>.<span style="color:#a6e22e">Foundation</span>.<span style="color:#a6e22e">BusinessOrchestration</span>.<span style="color:#a6e22e">BaseInput</span>)<span style="color:#f92672">:</span> Promise<span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Mock the execution of the LBO call
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">input</span> <span style="color:#66d9ef">instanceof</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">LBOS</span>.<span style="color:#a6e22e">Cmf</span>.<span style="color:#a6e22e">Foundation</span>.<span style="color:#a6e22e">BusinessOrchestration</span>.<span style="color:#a6e22e">TableManagement</span>.<span style="color:#a6e22e">InputObjects</span>.<span style="color:#a6e22e">ResolveSmartTableInput</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">output</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">LBOS</span>.<span style="color:#a6e22e">Cmf</span>.<span style="color:#a6e22e">Foundation</span>.<span style="color:#a6e22e">BusinessOrchestration</span>.<span style="color:#a6e22e">TableManagement</span>.<span style="color:#a6e22e">OutputObjects</span>.<span style="color:#a6e22e">ResolveSmartTableOutput</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Validate Service Requests
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">input</span>.<span style="color:#a6e22e">SmartTable</span>.<span style="color:#a6e22e">Name</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">be</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;TestSmartTable&#34;</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">input</span>[<span style="color:#e6db74">&#34;Values&#34;</span>].<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#34;Resource&#34;</span>)).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">be</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;Test&#34;</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">input</span>[<span style="color:#e6db74">&#34;Values&#34;</span>].<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#34;Name&#34;</span>)).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">be</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;IsEnabledFunctionalityName&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Inject reply
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#a6e22e">output</span>.<span style="color:#a6e22e">Result</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>                    [<span style="color:#e6db74">`T_ST_</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">input</span>.<span style="color:#a6e22e">SmartTable</span>.<span style="color:#a6e22e">Name</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>]<span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>                        {
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">&#34;Resource&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Test&#34;</span>,
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">&#34;ResourceType&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">&#34;Name&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;IsEnabledFunctionalityName&#34;</span>,
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">&#34;Value&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;functionalityName&#34;</span>,
</span></span><span style="display:flex;"><span>                            <span style="color:#e6db74">&#34;Area&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;TestArea&#34;</span>
</span></span><span style="display:flex;"><span>                        }
</span></span><span style="display:flex;"><span>                    ]
</span></span><span style="display:flex;"><span>                };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> Promise.<span style="color:#a6e22e">resolve</span>(<span style="color:#a6e22e">output</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> Promise.<span style="color:#a6e22e">reject</span>(<span style="color:#e6db74">&#39;Unexpected input type.&#39;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If the request is what we want to test, we will check it&rsquo;s inputs and inject our mock output.</p>
<p>Ok, enough setting up, let&rsquo;s build a test. I like to start with fail scenarios, in this case if the <code>entityIn</code> input is not an entity the task will send an error saying &ldquo;Please provide a valid entity&rdquo;. Pretty obvious, right? Let&rsquo;s do it!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>    <span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#34;should get fail due to incorrect entity input&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">customResolveMetadataFeatureTestFactory</span>(<span style="color:#66d9ef">undefined</span>,
</span></span><span style="display:flex;"><span>            (<span style="color:#a6e22e">outputs</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;&gt;</span>) =&gt; {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Trigger an entityIn with an invalid entity
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;entityIn&#39;</span>).<span style="color:#a6e22e">emit</span>({});
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;activate&#39;</span>).<span style="color:#a6e22e">emit</span>(<span style="color:#66d9ef">true</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            }, (<span style="color:#a6e22e">changes</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Changes</span>) =&gt; {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Validate the input
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">changes</span>[<span style="color:#e6db74">&#34;error&#34;</span>].<span style="color:#a6e22e">currentValue</span>.<span style="color:#a6e22e">message</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;Please provide a valid entity&#34;</span>);
</span></span><span style="display:flex;"><span>            });
</span></span><span style="display:flex;"><span>    });
</span></span></code></pre></div><p>We have two phases, the trigger phase and validate phase. In the trigger we inject an <code>activate</code> <em>true</em> and we give an invalid value for the <code>entityIn</code>. In the validate phase we use chai to make an assertion saying that we are expecting a specific error message.</p>
<p>Now let&rsquo;s move on to a happy test scenario.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>    <span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#34;should get success when activated&#34;</span>, (<span style="color:#a6e22e">done</span>) =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">entity</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">System</span>.<span style="color:#a6e22e">LBOS</span>.<span style="color:#a6e22e">Cmf</span>.<span style="color:#a6e22e">Navigo</span>.<span style="color:#a6e22e">BusinessObjects</span>.<span style="color:#a6e22e">Resource</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">entity</span>.<span style="color:#a6e22e">Name</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Test&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">barcode</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;testBarcode&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">customResolveMetadataFeatureTestFactory</span>(<span style="color:#66d9ef">undefined</span>,
</span></span><span style="display:flex;"><span>            (<span style="color:#a6e22e">outputs</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Map</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">string</span>, <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Output</span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">any</span><span style="color:#f92672">&gt;&gt;</span>) =&gt; {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Trigger an output
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;barcodeIn&#39;</span>).<span style="color:#a6e22e">emit</span>(<span style="color:#a6e22e">barcode</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;entityIn&#39;</span>).<span style="color:#a6e22e">emit</span>(<span style="color:#a6e22e">entity</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;eventType&#39;</span>).<span style="color:#a6e22e">emit</span>(<span style="color:#e6db74">&#34;testEvent&#34;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">outputs</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;activate&#39;</span>).<span style="color:#a6e22e">emit</span>(<span style="color:#66d9ef">true</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            }, (<span style="color:#a6e22e">changes</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Task</span>.<span style="color:#a6e22e">Changes</span>) =&gt; {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Validate the input
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">changes</span>[<span style="color:#e6db74">&#34;entityOut&#34;</span>].<span style="color:#a6e22e">currentValue</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#a6e22e">entity</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">changes</span>[<span style="color:#e6db74">&#34;barcodeOut&#34;</span>].<span style="color:#a6e22e">currentValue</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#a6e22e">barcode</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">changes</span>[<span style="color:#e6db74">&#34;activateOut&#34;</span>].<span style="color:#a6e22e">currentValue</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#66d9ef">true</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">changes</span>[<span style="color:#e6db74">&#34;notActivateOut&#34;</span>]).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#66d9ef">undefined</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">changes</span>[<span style="color:#e6db74">&#34;success&#34;</span>].<span style="color:#a6e22e">currentValue</span>).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#66d9ef">true</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Validate the persistency
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">persistedValue</span> <span style="color:#f92672">=</span> Object.<span style="color:#a6e22e">values</span>(<span style="color:#a6e22e">dataStoreMock</span>[<span style="color:#e6db74">&#34;_persistedDataStore&#34;</span>].<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;TestSmartTablePersisted&#39;</span>))[<span style="color:#ae81ff">0</span>][<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#34;result&#34;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">persistedValue</span>[<span style="color:#e6db74">&#34;Area&#34;</span>]).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;TestArea&#34;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">persistedValue</span>[<span style="color:#e6db74">&#34;Name&#34;</span>]).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;IsEnabledFunctionalityName&#34;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">persistedValue</span>[<span style="color:#e6db74">&#34;Resource&#34;</span>]).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;Test&#34;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">chai</span>.<span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">persistedValue</span>[<span style="color:#e6db74">&#34;Value&#34;</span>]).<span style="color:#a6e22e">to</span>.<span style="color:#a6e22e">equal</span>(<span style="color:#e6db74">&#34;functionalityName&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Report the test as a success
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">done</span>();
</span></span><span style="display:flex;"><span>            });
</span></span><span style="display:flex;"><span>    });
</span></span></code></pre></div><p>Wow, that&rsquo;s a lot more code, but if you take a closer look, it&rsquo;s all just assertions. Same as before, we have two phases, in the trigger phase we will send some values. In the validate phase we have two different validations. We validate the outputs of the task and also the persistency layer. Remember that this task, not only emits outputs, but also persists information. Also, now we create a know entity called <code>Resource</code> to surpass the guard we saw in the previous test.</p>
<h2 id="bonus-content---coverage">Bonus Content - Coverage</h2>
<p>A very interesting advantage of these kinds of tests is that it&rsquo;s very easy to extract metrics. The scaffolding already has in the package.json the command <code>vs:test</code> defined. In order to extract coverage you just need to run <code>npm run vs:test</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#a6e22e">Statements</span>   <span style="color:#f92672">:</span> <span style="color:#ae81ff">67.89</span><span style="color:#f92672">%</span> ( <span style="color:#ae81ff">203</span><span style="color:#f92672">/</span><span style="color:#ae81ff">299</span> )
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Branches</span>     <span style="color:#f92672">:</span> <span style="color:#ae81ff">50.21</span><span style="color:#f92672">%</span> ( <span style="color:#ae81ff">117</span><span style="color:#f92672">/</span><span style="color:#ae81ff">233</span> )
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Functions</span>    <span style="color:#f92672">:</span> <span style="color:#ae81ff">61.82</span><span style="color:#f92672">%</span> ( <span style="color:#ae81ff">34</span><span style="color:#f92672">/</span><span style="color:#ae81ff">55</span> )
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Lines</span>        <span style="color:#f92672">:</span> <span style="color:#ae81ff">73.73</span><span style="color:#f92672">%</span> ( <span style="color:#ae81ff">188</span><span style="color:#f92672">/</span><span style="color:#ae81ff">255</span> )
</span></span></code></pre></div><p>I saw an interesting post talking about what this means feel free to take a look here <a href="https://www.softwaretestinghelp.com/code-coverage-tutorial/" target="_blank" rel="noopener">softwaretestinghelp</a>. In order to generate this report we are using <a href="https://github.com/cobertura/cobertura" target="_blank" rel="noopener">cobertura</a> and <a href="https://www.npmjs.com/package/mocha-junit-reporter" target="_blank" rel="noopener">mocha-junit-reporter</a>.</p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Database Driver</title><link>https://devblog.criticalmanufacturing.com/blog/20230327_iot_database_driver/</link><pubDate>Mon, 27 Mar 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230327_iot_database_driver/</guid><description>Small demo showcase of driver database mssql</description><content:encoded><![CDATA[<p>We are going to do a walkthrough of how the Connect IoT Database driver works. This is focused on snippet use cases and not on an exhaustive property by property use case.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Should you use it?</li>
<li>Pre-requisites and setting up an environment</li>
<li>Connecting to the Database</li>
<li>Executing Queries</li>
<li>Database Polling</li>
</ol>
<h2 id="should-you-use-it">Should you use it?</h2>
<p>No&hellip; ok blog post ended&hellip;. .
<br></br>
I am hearing you now in my mind, &ldquo;but it&rsquo;s really the only way&rdquo;, &ldquo;we really have to use it&rdquo;. Ok ok, settle down. I will try to make a case not to use this approach.</p>
<ol>
<li>
<p><strong>Separation of Concerns</strong> - Databases are focused in persisting information in a structured way (for relational database). They are not concerned with guard clauses or with interfaces.</p>
</li>
<li>
<p><strong>Change</strong> - Databases change over time, they can have schema changes, and because you don&rsquo;t have an interface that is contracting between you and the database, things may change that impact you, without you knowing. Also, partition changes, fail-overs, load balancing, etc are a host of concerns that you are forced to have when interacting with a database, so there are a lot of points of failure.</p>
</li>
<li>
<p><strong>Catastrophic Failure</strong> - A database may not be the brain of a system, but it is it&rsquo;s memory. If you are directly interacting the database the margin for catastrophic failure is much higher. When you have an API in front of the database, the api can guard against bad requests and by being a controlled point of entry to the database can optimize for caching and for indexing, so when you bypass all that, a bad write may crash or throttle the database.</p>
</li>
<li>
<p><strong>Security</strong> - A database should be a highly secure and the most protected component of your application. It may have sensitive data and is the greatest point of failure of an application. Having a third party application as Connect IoT doing requests is an easy security exploit. Imagine the following scenario: Upon configuring Connect IoT, by mistake the user given has admin privileges in the database, so someone decides to execute a query for droping tables, you can see how that can be an issue. Ok, this is a pretty dramatic example, but also bear in mind that the database was not made to enforce great role based levels of security, so it&rsquo;s common to end up with much more permissions than you need, and if you have permission you gain responsibility for any future problem down the line.</p>
</li>
</ol>
<p>So the database driver is one of those Uncle Ben&rsquo;s kind of things: &ldquo;With great power comes great responsibility&rdquo;. Disclaimer aside, let&rsquo;s get rolling.</p>
<p><br></br></p>
<hr>
<h2 id="pre-requisites-and-setting-up-an-environment">Pre-requisites and setting up an environment</h2>
<p>Currently, Connect IoT only supports <strong>MSSQL</strong> databases. The way it&rsquo;s implemented it&rsquo;s not hard to add engines for other types like oracle, but we haven&rsquo;t had a use case for that yet and for the user that would be abstracted, so what&rsquo;s valid for MSSQL is valid for everything else.</p>
<p>For this example, I am going to use a windows pc, with wsl and I am going to use a local mssql database as a container. Microsoft has made a great tutorial on this and that is what I used <a href="https://learn.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker?view=sql-server-ver16&amp;pivots=cs1-bash" target="_blank" rel="noopener">MSSQL Tutorial</a>. I will assume that you have a CM MES system available to use.</p>
<p><br></br></p>
<hr>
<h2 id="connecting-to-the-database">Connecting to the Database</h2>
<p>So, first things first, lets create a database. I created a TestDB by running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">DATABASE</span> TestDB;
</span></span></code></pre></div><p>Now in the Microsoft SQL Server Management Studio we will have our database:</p>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_mssms.png" alt="MSSQL Tutorial Management Studio" />

</p>
<p>Now, we have a sql database up and running. 👏 👏</p>
<ol>
<li>
<p><strong>Create</strong> - Automation Protocol for the database package. Feel free to already:</p>
<ol>
<li>Change server to the correct IP</li>
<li>Change password to the correct User (i.e sa)</li>
<li>Change password to the correct Password (i.e 123456789)</li>
<li>Change database name to the generated database (in this case TestDB)</li>
</ol>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_protocol_1.png" alt="Automation Protocol - General Data" />

</p>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_protocol_2.png" alt="Automation Protocol - Parameters" />

</p>
</li>
<li>
<p><strong>Create</strong> - Automation Driver Definition, for now, just leave everything empty (use the previous created protocol), we just want to test the connection.</p>
</li>
<li>
<p><strong>Create</strong> - Automation Controller (use the automation driver definition created) and also leave as default</p>
</li>
<li>
<p><strong>Create</strong> - Automation Manager and Connect to a Resource</p>
</li>
<li>
<p><strong>Download and Run</strong> - Automation Manager</p>
</li>
</ol>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_running.png" alt="Automation Running - Connect to database" />

</p>
<p><br></br></p>
<hr>
<h2 id="executing-queries">Executing Queries</h2>
<p>We now have a running Connect IoT Manager and it connects to the database, hurray 👏 👏</p>
<p>Now for the fun stuff, let&rsquo;s start of by doing a Select Query. But before that run, this will create a table and insert some dummy data:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span>USE [TestDB];
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> [dbo].TestTable(
</span></span><span style="display:flex;"><span>	[A] [int] <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[name] [nvarchar](<span style="color:#ae81ff">50</span>) <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>	[quantity] [int] <span style="color:#66d9ef">NULL</span>
</span></span><span style="display:flex;"><span>) <span style="color:#66d9ef">ON</span> [<span style="color:#66d9ef">PRIMARY</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> TestTable <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#39;banana&#39;</span>, <span style="color:#ae81ff">150</span>); 
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> TestTable <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">2</span>, <span style="color:#e6db74">&#39;orange&#39;</span>, <span style="color:#ae81ff">154</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">GO</span>
</span></span></code></pre></div><p>Explaining a bit how this works. A query is a command and a command has parameters. So for example if I have <code>Select A as Magic from TestTable where A = 1</code>. The Connect IoT database driver interprets this as a Command of type <code>Select</code> and argument <code>TestTable</code>. Then it has parameters, in the parameters is where things get spicy.</p>
<p>We have two different concepts <code>Parameter Value</code> and <code>Parameter Query</code>, this matches the format of the query, the query has two parts one for defining what you want to Select the <code>Parameter Value</code> and what are the conditions for that selection <code>Parameter Query</code>.</p>
<p>In this example, we would add two parameters, one for the selection and another for the condition. So we can add one, for example Magic of type String, Column Name A and Protocol Data Type INT and is a ParameterValue. Then we need the condition, can be of Name MagicCondition and type String, Protocol Data Type INT, Column Name A and type ParameterQuery. In the ParameterQuery we can say that it has default value 1 and Operation is an Equal.</p>
<p><strong><em>Key Concept:</em></strong> In other words the decomposition of <code>Select A as Magic from TestTable where A = 1</code> would be -&gt; <code>[Command Type] [ParameterValue1, ..., ParameterValueN] [Command Argument] [ParameterQuery1, ..., ParameterQueryN]</code>.</p>
<p>For defining the output, the database driver gives you 3 modes, one if you have multiple results, so if your query will return multiple rows <code>Array of Results</code>, other if you are expecting multiple outputs of just one row <code>Single Result</code>, which will output a key-value pair of column name and value and finally one if you just want one value of a column <code>Single Value</code>.</p>
<hr>
<h3 id="recap"><strong>Recap</strong></h3>
<p>We want a Command <code>Select</code>:</p>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_driverdefinition.png" alt="Automation Driver Definition - Command Select" />

</p>
<p>And our two Command Parameters:</p>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_driverdefinition_param1.png" alt="Automation Driver Definition - Command Select ParamValue" />

</p>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_driverdefinition_param2.png" alt="Automation Driver Definition - Command Select ParamQuery" />

</p>
<hr>
<p>Now let&rsquo;s go back to the Automation Controller view and edit our workflow. Now we have a new command, so drag and drop the <code>Execute Equipment Command</code> task and we can select our command.</p>
<p>We are just testing it out, so I am going to also drag a <code>Timer</code> task and I&rsquo;m going to connect my timestamp to Activate the Execute Equipment Command and also drag a <code>Log Message</code> task to log the reply. Now we can start the automation manager and take a look at the console.</p>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_workflow1.png" alt="Automation Controller - Command Select Workflow" />

</p>
<p>Taking a look at the console we can see an array of key-value pairs with column name and the value.</p>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_output1.png" alt="Console Output - Command Select Workflow" />

</p>
<p>Ok, so we got the basics rolling. There are other commands particularly the Raw command if you want to do very advanced queries without having to declare everything, it&rsquo;s not advisable, because you lose transparency. Nevertheless, these concepts of <code>ParameterValue</code> and <code>ParameterQuery</code> hold throughout.</p>
<p><br></br></p>
<hr>
<h2 id="database-polling">Database Polling</h2>
<p>I would say 9 out 10 times, you need the database driver to do some kind of polling. Basically, you want to be notified when something changes, either a row changes or a value on a row or a row is added. You could do that already with timers and a clever use of the command tasks, but the driver already implements a configurable way to do that. These will be what the driver considers as events.</p>
<p>It has two modes, <code>FireAndForget</code> and <code>ContinueOnProcessingConfirmation</code>, as the name entails, the fire and forget emits whenever a change is detected, the continue on processing confirmation will only continue looking for changes when the first change is acknowledge or timed out.</p>
<p>For this example, we will focus on the <code>ContinueOnProcessingConfirmation</code>. The use case will be very simple, we want to log whenever a new row is inserted in the table and wait for 1s, before proceeding.</p>
<ol>
<li><strong>Create an Event</strong> - Go back to the driver definition and now create an event. The event will be looking at our table TestTable, we need to give it a column for it to use as reference (using <code>*</code> will not work) in our case I used <code>A</code> and we also have to give a column for the order by column I also gave it <code>A</code>. We are checking for new rows, that is why we selected <code>OnNewRows</code>. We also wan to to wait for confirmation, so we left of <code>ContinueOnProcessingConfirmation</code>.</li>
</ol>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_driverdefinition_event.png" alt="Automation Driver Definition - Event" />

</p>
<ol start="2">
<li><strong>Create a ConfirmDataProcessing Command</strong> - Remember that the event will be produced and will await for confirmation of being processed, in order to do that we will create a Command of type <code>ConfirmDataProcessing</code>. The Command also requires a Command Parameter of type <code>EventReply</code> which will specify to what event you are confirming.</li>
</ol>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_driverdefinition_event_confirmation.png" alt="Automation Driver Definition - Event" />

</p>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_driverdefinition_event_confirmation_param.png" alt="Automation Driver Definition - Event Confirmation Parameter" />

</p>
<ol start="3">
<li><strong>Edit Workflow</strong> - In the Automation Controller worflow add an <code>On Equipment Event</code>, <code>Timer</code>, <code>Execute Equipment Event</code> and <code>Log Message</code> task. On the equipment event task and equipment command should the respective events and commands. Use the event task to log and to activate the timer. The <code>Success</code> of the timer will activate the confirmation command. Change the timer to not auto activate and to be of type sleep, and decrease the timeout.</li>
</ol>
<p><strong><em>NOTE:</em></strong> Timers are only activated by a boolean input (1, 0, true, false), so you will need to add a converter to constant true for <code>$A</code>. Timer timeout must be lesser than the timeout of the confirmation timeout, as the driver will discard value if it has finished the defined amount of retries (in our case 3).</p>
<p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_controller_event.png" alt="Automation Controller - Event Confirmation" />

</p>
<ol start="4">
<li><strong>Run Automation Manager</strong> - If you already have data in the table you can either go to the driver definition and check the <code>Ignore Initial Values</code>. I deleted all rows that I had from the previous example and will now insert some rows.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> TestTable <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#39;banana&#39;</span>, <span style="color:#ae81ff">150</span>); 
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> TestTable <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">2</span>, <span style="color:#e6db74">&#39;orange&#39;</span>, <span style="color:#ae81ff">151</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> TestTable <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">3</span>, <span style="color:#e6db74">&#39;grape&#39;</span>, <span style="color:#ae81ff">152</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> TestTable <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">4</span>, <span style="color:#e6db74">&#39;apple&#39;</span>, <span style="color:#ae81ff">153</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> TestTable <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">5</span>, <span style="color:#e6db74">&#39;tangerine&#39;</span>, <span style="color:#ae81ff">154</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> TestTable <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">6</span>, <span style="color:#e6db74">&#39;mango&#39;</span>, <span style="color:#ae81ff">155</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> TestTable <span style="color:#66d9ef">VALUES</span> (<span style="color:#ae81ff">7</span>, <span style="color:#e6db74">&#39;blueberry&#39;</span>, <span style="color:#ae81ff">156</span>);
</span></span></code></pre></div><p>
<img src="/blogPosts/IoT/20230327_iot_database_driver/iot_database_driver_automation_output_event_1.png" alt="Console Output - Event Confirmation" />

</p>
<p>What is happening is the Automation Manager starts and performs the trigger query and sees no values. After that we executed the inserts, so now the database has 7 columns. The driver detects a change and is now going to process one by one and wait for confirmation. We could have it to process all the rows after a change by selecting the result data format <code>AllNew</code>, but normally for confirmation scenarios, you want to process one by one, to assure processing.</p>
<p>This finishes our post, as you can see it is a bit complex when you first look at it, but after understanding the driver syntax it becomes quite powerful.</p>
<p><br></br></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>Using the MES as the Owner of the Configuration</title><link>https://devblog.criticalmanufacturing.com/blog/20230413_iot_mes_as_configuration_owner/</link><pubDate>Mon, 27 Mar 2023 00:00:00 +0000</pubDate><dc:creator>João Roque</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20230413_iot_mes_as_configuration_owner/</guid><description>Small demo showcase of driver database mssql</description><content:encoded><![CDATA[<p>There are different patterns and anti-patterns for storing information critical to the integration on MES. I will try to address those scenarios.</p>
<h2 id="topics">Topics</h2>
<ol>
<li>Configuration</li>
<li>Anti-Pattern</li>
<li>Pattern</li>
</ol>
<h2 id="configuration">Configuration</h2>
<p>The Automation Manager on boot, already retrieves a lot of context information, such as what drivers to start and what is the logic in the Automation Controllers that it will need to execute. Nevertheless, when implementing an integration scenario sometimes it&rsquo;s very helpful to try and maintain our controller more abstract and have a dynamic resolution for some configuration such as ports, ips, what interfaces to enable, etc.</p>
<p>If we for example, want to use an Automation Manager to control several <code>Resources</code>, but want to reuse the same Automation Driver and Automation Controller we want to be dynamic enough in our Automation Controller to support this scenario.</p>
<h2 id="anti-pattern">Anti-Pattern</h2>
<p>The common anti-patterns are two different approaches that seem ok, but don&rsquo;t scale or obfuscate too much.</p>
<h3 id="workflow---static">Workflow - Static</h3>
<p>The first one is a no-brainer, just leave it static, leave it in the <code>OnEquipmentSetup</code> or in the driver definition. This works well for immutable configurations, things that won&rsquo;t change, when reusing the controller. This obviously is not very dynamic, because if Resource A and Resource B need different ports or ip, or any other configuration we will need to create a new Automation Controller.</p>
<p>
<img src="/blogPosts/IoT/20230413_iot_mes_as_configuration_owner/iot_mes_as_configuration_owner_static_config.png" alt="Static Configuration" />

</p>
<p>As we can see in this example, we have the <code>ResourceA</code> as part of file Path, if we want to reuse this implementation for <code>ResourceB</code> it won&rsquo;t work.</p>
<h3 id="workflow---tokens">Workflow - Tokens</h3>
<p>One of the clever solutions we can use is tokenize, so instead of explicitly stating <code>ResourceA</code> we can write <code>${entityName}</code> and Connect IoT will replace it in runtime with the correct value for the entity that is appended to the execution.</p>
<p><strong>NOTE</strong>: In my opinion, I don&rsquo;t like tokenization, because it results in obfuscation of the code and that is why for me it&rsquo;s an anti pattern. It adds cognitive load to whomever is reading the workflow to know that tokens exist and to what they are replaced with. So I would advise caution when using tokens, but it has helped solve problems in the past, so it&rsquo;s important to mention it.</p>
<h2 id="pattern">Pattern</h2>
<p>The common patterns described here, may not be the only way, but are beaten paths that have worked well in the past.</p>
<p><strong>NOTE</strong>: One consideration that always has to be made is the level of dynamism in correlation to the addition of complexity in you workflow.</p>
<h3 id="leveraging-the-mes-entity">Leveraging the MES Entity</h3>
<p>If the Connect IoT Controller is running embedded with the context of a MES Entity, it seems obvious to add the needed configuration to that context. This works well when we require few configurations and we can solve our issue just by adding attributes to our entity.</p>
<p>
<img src="/blogPosts/IoT/20230413_iot_mes_as_configuration_owner/iot_mes_as_configuration_owner_entity_attributes_workflow_tasks.png" alt="Workflow Tasks" />

</p>
<p>
<img src="/blogPosts/IoT/20230413_iot_mes_as_configuration_owner/iot_mes_as_configuration_owner_entity_attributes_workflow.png" alt="Entity Instance Task" />

</p>
<p>So this seems like it solves all problems right? Well take a look at this Resource:</p>
<p>
<img src="/blogPosts/IoT/20230413_iot_mes_as_configuration_owner/iot_mes_as_configuration_owner_entity_attributes_resource.png" alt="Resource Attributes" />

</p>
<p>We can see the list grows fast and bloats the definition of the resource. It also forces us to create some kind of query if we want to see all the configurations for Connect IoT. So this can be a too decentralized solution. It also does not allow for more dynamic resolution than just the entity, like a particular vendor, type, etc. Also, the type of user with access to the Resource Entity, may not be the user you want to have access to these types of configurations.</p>
<h3 id="using-configs">Using Configs</h3>
<p>An alternative, is the use of an MES Configuration. We can create a tree of configurations and then retrieve them in the workflow before starting. We can define the config tree appropriate to our needs and then use an <code>Execute Service</code> task to retrieve our configs. We can get the configurations with the service <code>GetConfigByPath</code>.</p>
<p>
<img src="/blogPosts/IoT/20230413_iot_mes_as_configuration_owner/iot_mes_as_configuration_owner_config_workflow_tasks.png" alt="Workflow Retrieve Configuration" />

</p>
<p>This will return a JSON object, which we will have to convert and extract the value.</p>
<p>
<img src="/blogPosts/IoT/20230413_iot_mes_as_configuration_owner/iot_mes_as_configuration_owner_config_workflow_converter.png" alt="Config Converter" />

</p>
<p>This approach, enables us to group the information in a Configuration tree and behind the Administration tab, giving a more secure and aggregated location to our implementation configurations.</p>
<p>
<img src="/blogPosts/IoT/20230413_iot_mes_as_configuration_owner/iot_mes_as_configuration_owner_config.png" alt="Configuration GUI" />

</p>
<h3 id="smart-table">Smart Table</h3>
<p>The last pattern we will discuss is the Smart Table pattern. This enables dynamic resolution of configurations based on several different columns. Let&rsquo;s try and see an example of this.</p>
<p>Imagine that depending on a context, for example, <code>Resource</code>, <code>Resource Type</code> or <code>Area</code>, we want to collect a particular subset of configurations. The configurations will have a name and value. We can create a Smart Table, like this:</p>
<p>
<img src="/blogPosts/IoT/20230413_iot_mes_as_configuration_owner/iot_mes_as_configuration_owner_config.png" alt="Smart Table GUI" />

</p>
<p>This enables us to have a priority of resolution, history of changes and also we can have DEE Rules triggered on changes.</p>
<p>In the workflow we can then use a <code>Resolve Table</code> task, that will automatically be populated with the inputs and outputs and will resolve the Smart Table according to what are the inputs given.</p>
<p>
<img src="/blogPosts/IoT/20230413_iot_mes_as_configuration_owner/iot_mes_as_configuration_owner_smarttable_task.png" alt="Resolve Table Task" />

</p>
<p>In this case we had <code>Name</code> and <code>Value</code>, to simplify you can have only value and then have the object to manipulate there. We can also access either the rows output which will have all the columns. This use case requires a bit of logic downstream to treat the value and use them.</p>
<p>An example of the rows output, would be something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;ROWID&#34;</span>: <span style="color:#e6db74">&#34;1&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Resource&#34;</span>: <span style="color:#e6db74">&#34;1805111613350000001&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Name&#34;</span>: <span style="color:#e6db74">&#34;Path&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Value&#34;</span>: <span style="color:#e6db74">&#34;C:\\&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;CreatedBy&#34;</span>: <span style="color:#e6db74">&#34;System&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;CreatedOn&#34;</span>: <span style="color:#e6db74">&#34;2018-05-11T15:13:42.030Z&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;ModifiedBy&#34;</span>: <span style="color:#e6db74">&#34;System&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;ModifiedOn&#34;</span>: <span style="color:#e6db74">&#34;2023-04-12T05:54:16.563Z&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;LastServiceHistoryId&#34;</span>: <span style="color:#e6db74">&#34;2304121347210000011&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;LastOperationHistorySeq&#34;</span>: <span style="color:#e6db74">&#34;638168756565633333&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;ROWID&#34;</span>: <span style="color:#e6db74">&#34;2&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Resource&#34;</span>: <span style="color:#e6db74">&#34;1805111613350000001&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Name&#34;</span>: <span style="color:#e6db74">&#34;Type&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Value&#34;</span>: <span style="color:#e6db74">&#34;Test&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;CreatedBy&#34;</span>: <span style="color:#e6db74">&#34;System&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;CreatedOn&#34;</span>: <span style="color:#e6db74">&#34;2020-10-01T20:31:46.333Z&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;ModifiedBy&#34;</span>: <span style="color:#e6db74">&#34;System&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;ModifiedOn&#34;</span>: <span style="color:#e6db74">&#34;2023-04-12T05:54:16.660Z&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;LastServiceHistoryId&#34;</span>: <span style="color:#e6db74">&#34;2304121347210000011&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;LastOperationHistorySeq&#34;</span>: <span style="color:#e6db74">&#34;638168756566600000&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>The goal would then be to extract the values and according to name create our configuration.</p>
<p>This is probably the most powerful and dynamic method of configuration, but as we saw, it requires some more configuration and know-how. So use it with balance and consideration, in order to not introduce too much complexity to our solution.</p>
<h2 id="summary">Summary</h2>
<p>There are multiple approaches to tackling the same issue. We should always strive for balance a solution that is simple, but retains an adequate level of dynamism. Unfortunately, this is not a one size fits all post, but I hope it helps illustrate the thought process that you must have when thinking about these types of issues.</p>
<p><br></br></p>
<hr>
<p><br></br></p>
<h2 id="author">Author</h2>
<h3 id="hello---my-name-is-joão-roque-">Hello 👏 , my name is João Roque ✌️</h3>
<p>I&rsquo;ve been working for some years at Critical Manufacturing. I split my time working in the IoT Team and working with the project&rsquo;s teams.
You can visit me at <a href="https://j-roque.com/" target="_blank" rel="noopener">https://j-roque.com/</a> or check me at <a href="https://www.linkedin.com/in/j-roque/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>Skills: Connect IoT / DevOps

<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_joao_roque.jpg"
    alt="João Roque"
    title="IoT Developer Advocate"
  />
  <figcaption style="text-align: center">IoT Developer Advocate</figcaption>
</figure>

</p>
]]></content:encoded></item></channel></rss>