Close
0%
0%

From I to O: Configuring Logic with Drawio

Adding diagram-based configuration to my data acquisition program with Draw.io for visualizing a normally text based process.

Public Chat
Similar projects worth following
This project aims to add a diagram-based configuration layer to dcafs, my cli based data acquisition tool, using Draw.io. The goal is to first provide an alternative to the normally text-based setup process and later add runtime enhancements like live annotation.

dcafs uses XML for setup, which works—but modular logic quickly turns into a maze of node-jumping. I needed a flowchart GUI, and draw.io provides that. Luckily, it’s all XML—which suits me fine

Already made a proof of concept that allows making sequences that:
* Wait on read from, or write to an attached sensor
* Add delays or trigger based on interval and time of day
* Conditional branching
* Counters for loops
* ...
Each of those represented with a single shape and sequences with arrows.

Current state: task,rtvals and gpio in
Next up: I2C, paths, ...

Some optional background

Dcafs is an ancient project (about 13 years old) that I keep working on from time to time. Recently, I decided to do a major rewrite, which accidently ended up moving towards a more reactive approach. The first core component to undergo a heavy revision is the TaskManager, where a task is a sequence of actions and checks.

This component adds the following functionality to dcafs.

  • Inititate actions base on time (delay, interval, clock)
  • Interacting with attached devices:
    • From sending a fixed message on a set interval to request data
    • Up to complete automation sequences spanning multiple devices and realtime data

These sequences were fairly straightforward in XML, as the code was flexible enough to handle the original use cases. However, after the rewrite, achieving 'readable' XML became a limiting factor.

Simple loops can be mimicked in XML but those need to be implemented. Loops in a diagram structure however, are inherent. The code remains the same, just need to beware of going infinite.

Something like this 'do x until y' aka retry logic, is still fairly straightforward. But problems arise if you want to go back or forward to a specific line/action. In XML this would involve splitting things up in smaller sections and giving those an id. Which in turn means having to scroll around the XML looking for the relevant node...

Imagine having to do this in XML (as user).

The primary reason for the project is that it seems like a fun challange, secondary was the added functionality. Potentially lowering the bar of entry to dcafs, is a nice bonus.


How draw.io will be used

Since there's no 'one style fits all' when it comes to diagrams, I wanted to keep it appearance-agnostic. As such, the only rule regarding visual aspects, is that every arrow must have a value (text displayed on it), which we'll call label from now on. This label is used to define the kind of relationship between the connected shapes.

The rest of the configuration is 'invisible', given it's done using shape properties (accessed by right clicking on a shape and selecting 'Edit data'). The mandatory property for any shape is dcafstype which defines what it should map to in code. Other required properties depend on this dcafstype.

Below is an example of two diagrams. Left and right result in the same logic, just different aesthetic. The bold text is just a suggestion, the flavor text underneath uses placeholders to show properties. It's those properties that actually define the logic. 

From shape to DrawioCell: parsing draw.io xml

For those that want to read source instead: Drawio.java (do note it's a first version, might not be final) with XMLdigger.java being the helper class that reads the xml. Some explanation of how that digger is used can be found here.

Below is a snippet showing how the majority of a draw.io xml file looks. I won't go into detail explaining it... because I don't know given I only cared about the things I needed anyway.


<mxfile agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.2.15 Chrome/134.0.6998.205 Electron/35.2.1 Safari/537.36" host="Electron" version="26.2.15" pages="4">
  <diagram id="IfgC_i9sJ90nQT9nQvYs" name="Basics">
    <mxGraphModel arrows="1" connect="1" fold="1" grid="1" gridSize="10" guides="1" math="0" page="1" pageHeight="1169" pageScale="1" pageWidth="827" shadow="0" tooltips="1">
      <root>
        <mxCell id="0" />
        <mxCell id="1" parent="0" />
        <mxCell edge="1" id="aRKCRy_f5RSsDbNbkwyN-4" parent="1" source="aRKCRy_f5RSsDbNbkwyN-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="aRKCRy_f5RSsDbNbkwyN-3" value="next">
          <mxGeometry as="geometry" relative="1" />
        </mxCell>

First pass 

When a property is added to a shape, the mxCell get encapsulated in an object node.

<object dcafstype="writerblock" id="aRKCRy_f5RSsDbNbkwyN-1" label="label;Runs:<span...
Read more »

  • Using GPIO

    Michiel TJampens3 days ago 0 comments

    Because rtvals are in, and i figure people here prefer actual hardware I started on adding GPIO's.


    The code is fairly easy because i use diozero, just extend FlagVal and add a listerener for isr.
    Input does add a couple properties:
    • gpio name
    • idle level (low,high)
    • pullogic (internal,external,none) (if internal, idle is translated to pullup/pulldown setup)
    • debounce time

    And two custom arrows besides all the flag ones.

    • pressed for idle to !idle
    • release for !idle to idle

    All the code related  to gpio can be found here and the parser addition here. It's only about 200 lines in total so not much to talk about.

    Below is the method that accept the isr event. Not much to it, but just to show that because it contains a timestamp the debouncing can be done be comparing those instead of an actual timer.

    public void accept(DigitalInputEvent event) {
            //Logger.info("Trigger: " + event);
            var newState = event.getValue();
            if (event.getEpochTime() - lastTrigger < debounceMs) {
                lastTrigger = event.getEpochTime();
                return;
            }
    
            if (newState) { //FALSE -> TRUE
                raiseBlock.start();
            } else { // TRUE -> FALSE
                fallBlock.start();
            }
            value = newState;
            lastTrigger = event.getEpochTime();
        }

    Now that GPIO are added, next will be I2C. Those are already a part of dcafs for years, but got an update a couple of months ago so stuff like this is possible.

    This is how to read the current temperature from a AHT20 sensor, convert it to scientific values and store in memory. Given it's a bit like 'lite' pathforward, i figured it's a suitable step towards it.

    <i2cop id="sample" info="Get a temp/hum measurement">
    	<write reg="0xAC">0x33 0x00</write> <!-- Trigger a conversion -->
    	<read delay="100ms" reg="0x71" bitsplit="8,20,20,8"/> 
    	<store group="aht20">
    		<!-- 8bits  -> i0 -> status (ignoring) -->
    		<int i="0" name="status"/>
    		<!-- 20bits -> i1 -> raw humidity -->
    		<int i="1" name="rawhum"/>
    		<!-- 20bits -> i2 -> raw temperature -->
    		<int i="2" name="rawtemp"/>
    		<!-- 8bits  -> i3 -> crc (ignored) -->
    		<int i="3" name="crc"/>
    	</store>
    	<math>
    		<op>i1=(i1/2^20)*100</op> <!-- Raw humidity to percentage -->
    		<op>i2=(i2/2^20)*200-50</op> <!-- Raw temperature to °C -->
    	</math>
    	<store group="aht20" db="data:aht20">
    		<real i="1" name="humidity" scale="1" unit="%"/>
    		<real i="2" name="temperature" scale="2" unit="°C"/>
    	</store>
    </i2cop>

  • Realtime Values added

    Michiel TJampens05/14/2025 at 15:15 0 comments

    Realtime Values aka rtvals added

    The first major step was writing the parser and adding tasks. That was described in the 'details'. It layed the groundwork for the 'active' side of diagrams. Meaning things that don't 'respond' but get triggered 'directly' (i know vague difference).

    This allowed for fairly straightforward addition of the first reactive component in the form of rtvals.

    In this update, realval and integerval were added. Which allows diagrams to react to updates of those. To take it a step further, I wanted derived values to be possible. The way i did this might be a bit odd... but it works (so far).

    Major difference with the active side is that condition blocks now get access to data within scope, which is the newly received value and the current value.

    Another new element is the math block, this requires a math parser, wanted to write my own and the result is explained here. It gets the same scope.

    But using the logic like it works in task wouldn't allow much flexibility. Which is why the update method inside a realval or integerval is this.

    public boolean update(double value) {
            var pre = preCheck.start(value, this.value);
            if (ignorePre || pre) {
                var res = math.eval(value, this.value, 0.0);
                var post = postCheck.start(value, this.value, res);
                if (ignorePost || post) {
                    this.value = res;
                    return true;
                }
            }
            return false;
        }

    This could be shown in a diagram like this:

    Those dotted lines are optional and taken care of by the parser.  It allows the use of the arrows on the conditional block without it affecting the update. The first one isn't used on the drawio side yet, but the second one is. If the condition block is placed in front of the Update real block, ignorePost is false. If after it, that becomes true. And just to not have to bother with null checks, no condtion is translated to a dummy one that doesn't do anything.

    Adding to that the earlier stated scope being available to both condition and math, and you get a setup that is really flexibile because those blocks are 'inside' the realval and get old and new data.

    Putting all that into action, allowed things like this:

    Incoming data refers to an update request for 'water_pressure', all the other values are updated because they are 'derived' from that val. Furthermore, as you can see in the bottom right, that's an 'active side' block, it's possible to start active sequences in an otherwise reactive diagram.

    If you wanted to put logic like that to use, an active side like this can be added. Active uses data updated by the reactive side to determine the sample rate of a sensor. The set/clear flag block is newly adding to make it visually clearer.But if you wanted it to be a bit more reactive, it would require the addition of a boolean val that can react based on an updated state. FlagVal was therefore added to the drawio options, with the option of 4 arrows that correspond to all the combinations of L and H.

    It's possible to take this a step further (but maybe to far) and get rid of the interval. This just keeps track of sample count instead of time passed.


    So now that the foundations are layed, it's possible to start on adding actual hardware. Next step is GPIO.

View all 2 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates