Apama Advent Calendar - 17 Dec 2021 - Enhancing Cloud Fieldbus using Apama

Level: ADVANCED

Cumulocity IoT Cloud Fieldbus is the standard approach to connect Fieldbus devices (e.g. Modbus) to Cumulocity IoT in a consistent manner. Cloud Fieldbus support is built into the Cumulocity IoT Linux agent and several gateways. With Cloud Fieldbus, device integration is defined in a device protocol that specifies the source of the data (device address, register) and how the data is mapped into the Cumulocity IoT data model.

Out of the box, Cloud Fieldbus only supports integer values to be read from registers. Some devices are reporting floating point numbers though (e.g. temperatures like 25.1 °C). There is no standardized way of doing this but typically IEEE-754 (Wikipedia) encoded floating point numbers are used. In some cases, numbers are just left-shifted (e.g. the 25.1 from the temperature reading is reported as 251).

Today, we will show you how to tackle the IEEE-754 decoding using Apama within Cumulocity IoT. Handling the left shift in a similar fashion should then be straightforward. The code can be used either in an EPL App or in a custom Analytics Builder block (this is already available for both 32 bit and 64 bit numbers in the open source analytics-builder-blocks-contrib project). We described adding custom block on Day #16.

In a single-precision (32 bit) IEEE-754 floating point number, the bits represent

  • Sign bit (1 bit)
  • Exponent (8 bits)
  • Fraction (23 bit)

For double-precision, it is still a single sign bit but 11 exponent bits and 53 fraction bits. Below you can see the EPL code for decoding a single-precision number in full. You can use the decoding by calling integerToFloat32 with an integer value and it returns a float. Internally, the code in lines 2 to 12 first converts the integer into a string of bits. From that string the sign, exponent, and fraction are extracted (lines 13-15). In line 16 the floating point number is calculated by combining the three: (-1.0).pow(sign) * fraction * (2.0).pow(e).

Code block #1:

action integerToFloat32(integer value) returns float {
    sequence<string> bits := [];
    integer i := 1 << 31;
    while(i>0) {
        if((value and i) > 0) {
            bits.append("1");
        } else {
            bits.append("0");
        }
 
        i := i / 2;
    }
    float sign := binaryToInteger(subsequence(bits,0,1)).toFloat();
    float e := (binaryToInteger(subsequence(bits,1,9)) - 127).toFloat();
    float fraction := calculateFraction(subsequence(bits,9,32));
    float r := (-1.0).pow(sign) * fraction * (2.0).pow(e);
    return r;
}
 
action binaryToInteger(sequence<string> input) returns integer {
    integer result := 0;
    integer i := 0;
    while(i<input.size()) {
        if(input[i]="1") {
            result := result + 2.pow(input.size()-i-1);
        }
        i := i + 1;
    }
    return result;
}
 
action subsequence(sequence<string> input, integer start, integer end) returns sequence<string> {
    sequence<string> output := [];
    integer i := start;
    while(i<end) {
        output.append(input[i]);
        i := i + 1;
    }
    return output;
}
 
action calculateFraction(sequence<string> input) returns float {
    float fraction := 1.0;
    integer i := 0;
    while(i<input.size()) {
        if(input[i]="1") {
            fraction := fraction + 2.0.pow(-1.0 * (i.toFloat() + 1.0));
        }
        i := i + 1;
    }
    return fraction;
}

You can use this code for example in an EPL App to convert all measurements of a certain type and store them as a new measurement:

Code block #2:

on all MeasurementFragment(valueFragment="TempInteger",valueSeries="T") as m {
        send MeasurementFragment("",
                "c8y_Temperature",
                    m.source,
                    m.time,
                    "C8Y_Temperature",
                    "T",
                    integerToFloat32(m.value.integralPart()),
                    m.unit,
                    new dictionary<string,any>) to MeasurementFragment.SEND_CHANNEL;
}

As a next exercise, you could modify the Linux Agent to send the original integer measurements only to Apama and not store them in Cumulocity IoT (Hint: Cumulocity IoT has a specific processing mode for this: Real-time processing - Cumulocity IoT Guides).

We expect you need a break after that, which is good, as it’s Friday. We’ll be back on Monday 20th, so enjoy your chocolates from behind the doors of a real advent calendar for the next couple of days.

Today’s article was kindly provided by Harald Meyer from the Global Competency Centre for IoT team.

The next article will be Monday 20 December - have a great weekend.


This is Day #17 of a short series of brief tips, tricks, hints, and reminders of information relating to the Apama Streaming Analytics platform, both from Software AG as well as from the community.
This series of articles will be published Monday-Friday only, with the occasional weekend bonus.