How to extend the Analytics Builder with custom blocks - Video

Overview

The Analytics Builder is a powerful tool for domain experts to define analytics models in Cumulocity IoT. It comes with tons of pre-build blocks to do complex analytics. But sometimes the pre-build blocks are not sufficient and you need to extend it with your own block.
In this video we show you how!

Interested in attending future IoT Developer events?
Join the Cumulocity IoT Developers group and get notified of the events that are coming up!

Presentation & code examples

Analytic Builder Block.pdf (1.1 MB)

01 Base

package com.example.analyticsbuilder.contrib;

using apama.analyticsbuilder.BlockBase;
using apama.analyticsbuilder.Activation;

event NormBlock {    
	BlockBase $base;
    
    action $process(Activation $activation, float $input_value) {
       
    }
    
    action<Activation, float> $setOutput_output;
}

02 Logic

package com.example.analyticsbuilder.contrib;

using apama.analyticsbuilder.BlockBase;
using apama.analyticsbuilder.Activation;

event NormBlock {    
    BlockBase $base;
    
    action $process(Activation $activation, float $input_x, float $input_y, optional<float> $input_z) {
    float result := ($input_x * $input_x + $input_y * $input_y).sqrt();
       $setOutput_output($activation, result);
    }
    
    action<Activation, float> $setOutput_output;
}

03 doc

package com.example.analyticsbuilder.contrib;

using apama.analyticsbuilder.BlockBase;
using apama.analyticsbuilder.Activation;

/**
* Norm.
*
* Calculate the euclidian norm of the given 2 or 3 input values.
* @$blockCategory Calculations
* @$derivedName Offset $offset
*/
event NormBlock {    
	
	BlockBase $base;
	/**
    * @param $input_x The input value of the x-axis.
    * @param $input_y The input value of the y-axis.
    * @param $input_z The input value of the z-axis.
    * @$inputName value X-Axis.
    * @$inputName value Y-Axis.
    * @$inputName value Z-Axis.
    */
    action $process(Activation $activation, float $input_x, float $input_y, optional<float> $input_z) {
    float result := ($input_x * $input_x + $input_y * $input_y).sqrt();
       $setOutput_output($activation, result);
    }
    
    /**
    * Output.
    *
    * The norm of the input vector.
    */
    action<Activation, float> $setOutput_output;
}

04 Build

analytics_builder build extension --input NormBlock --output norm-block.zip
analytics_builder  build extension --input ./NormBlock --cumulocity_url https://lora-sandbox.eu-latest.cumulocity.com --username mario.heidenreich@softwareag.com --password <password> --restart --ignoreVersion --name NormBlock

05 AB Cli

analytics_builder list extensions --cumulocity_url https://lora-sandbox.eu-latest.cumulocity.com --username mario.heidenreich@softwareag.com --password Bootc4mp
analytics_builder build extension --input samples/blocks --output sample-blocks.zip

analytics_builder upload extension --input NormBlock.zip --cumulocity_url https://lora-sandbox.eu-latest.cumulocity.com --username mario.heidenreich@softwareag.com --password <password>--restart --ignoreVersion

06 Params & State

package com.example.analyticsbuilder.contrib;

using apama.analyticsbuilder.BlockBase;
using apama.analyticsbuilder.Activation;
using apama.analyticsbuilder.L10N;
using apama.analyticsbuilder.Value;

event NormBlock_$Parameters {
    /**
    * Threshold value.
    *
    * The minimal change to trigger an output.
	* Must be positive.
    */
	float threshold;
	
	constant float $DEFAULT_threshold := 0.01;
	
	action $validate() {
	    if threshold < 0.0 or not threshold.isFinite() {
	        throw L10N.getLocalizedException(
	            "block_msg_com.cumulocity.analyticbuilder.contrib.NormBlock_outOfRange", [<any> threshold]);
		}
	}
}

event NormBlock_$State {
    optional<float> oldValue;
}


/**
* Norm.
*
* Calculate the euclidian norm of the given 2 or 3 input values.
* @$blockCategory Calculations
* @$derivedName Offset $offset
*/
event NormBlock {    
	
	BlockBase $base;
	
	NormBlock_$Parameters $parameters;
	
	/**
    * @param $input_x The input value of the x-axis.
    * @param $input_y The input value of the y-axis.
    * @param $input_z The input value of the z-axis.
    * @$inputName value X-Axis.
    * @$inputName value Y-Axis.
    * @$inputName value Z-Axis.
    */
    action $process(Activation $activation, float $input_x, float $input_y, optional<float> $input_z, NormBlock_$State $blockState) {
    	float result := ($input_x * $input_x + $input_y * $input_y).sqrt();
    	ifpresent $input_z {
    		result := ($input_x * $input_x + $input_y * $input_y + $input_z * $input_z).sqrt();
	    }
	    if $parameters.threshold > 0. {
		    ifpresent $blockState.oldValue as old {
		    	if not (old - result).abs() > $parameters.threshold {
		    		return;
		    	}
		    }
	    } 
	    $blockState.oldValue := result;
	    $setOutput_output($activation, result);
    }
    
    /**
    * Norm.
    *
    * The norm of the input vector.
    */
    action<Activation, float> $setOutput_output;
    
    action $validate(dictionary<string,any> $modelScopeParameters) {
	 if $base.getInputCount("x") = 0 or $base.getInputCount("y") = 0 {
		 throw L10N.getLocalizedException(
	                  	            "block_msg_com.cumulocity.analyticbuilder.contrib.NormBlock_wrongInputCombo", new sequence<any>);
	 }
	}
}

07 Test Project

<?xml version="1.0" encoding="utf-8"?>
<pysysproject>
	<requires-pysys>1.3.0</requires-pysys>
	<requires-python>3</requires-python>


	<property name="ANALYTICS_BUILDER_SDK"                 value="${env.ANALYTICS_BUILDER_SDK}"/>


	<property name="SOURCE"                  value="${root}"/>
	<property root="testRootDir"/>
	<property environment="env"/>
	<property osfamily="osfamily"/>

	<property name="APAMA_HOME"              value="${env.APAMA_HOME}"/>
	<property name="APAMA_WORK"              value="${env.APAMA_WORK}"/>

	<property name="defaultAbortOnError" value="true"/>
	<property name="defaultIgnoreExitStatus" value="false"/>
	<property name="defaultEnvironsTempDir" value="self.output"/>
	<property name="redirectPrintToLogger" value="false"/>
	<property name="verboseWaitForSignal" value="true"/>

	<property name="shutdownApamaComponentsAfterTest" value="true"/>



	<runner classname="ApamaRunner" module="apama.runner"/>

	<path value="${ANALYTICS_BUILDER_SDK}/testframework"/>

	<test-plugin classname="apama.testplugin.ApamaPlugin" alias="apama"/>

</pysysproject>

08 Test

__pysys_title__   = r""" Norm Block - Test the basic calculation """ 
#                        ================================================================================
__pysys_purpose__ = r""" """ 
	
__pysys_authors__ = "marh"
__pysys_created__ = "2022-11-11"
#__pysys_skipped_reason__   = "Skipped until Bug-1234 is fixed"

#__pysys_traceability_ids__ = "Bug-1234, UserStory-456" 
#__pysys_groups__           = "myGroup, disableCoverage, performance"
#__pysys_modes__            = lambda helper: helper.inheritedModes + [ {'mode':'MyMode', 'myModeParam':123}, ]
#__pysys_parameterized_test_modes__ = {'MyParameterizedSubtestModeA':{'myModeParam':123}, 'MyParameterizedSubtestModeB':{'myModeParam':456}, }

import pysys
from pysys.constants import *
from apamax.analyticsbuilder.basetest import AnalyticsBuilderBaseTest

class PySysTest(AnalyticsBuilderBaseTest):
    def execute(self):
        correlator = self.startAnalyticsBuilderCorrelator(blockSourceDir=f'{self.project.root}/blocks/')
        modelId = self.createTestModel('com.example.analyticsbuilder.contrib.NormBlock')
        self.sendEventStrings(correlator,
                              self.timestamp(1),
                              self.inputEvent('x', 0, id=modelId),
                              self.timestamp(2),
                              self.inputEvent('y', 1, id=modelId),
                              self.timestamp(2.1),
                              )
		
    def validate(self):
        self.assertGrep('correlator.log', '(ERROR|FATAL|WARN .*Failed to parse) .*', contains=False, ignores=[])
        self.assertBlockOutput('output', [0.0, 1.0])

09 Test Params

__pysys_title__   = r""" Norm Block - Parameter tests """ 
#                        ================================================================================
__pysys_purpose__ = r""" """ 
	
__pysys_authors__ = "marh"
__pysys_created__ = "2022-11-11"
#__pysys_skipped_reason__   = "Skipped until Bug-1234 is fixed"

#__pysys_traceability_ids__ = "Bug-1234, UserStory-456" 
#__pysys_groups__           = "myGroup, disableCoverage, performance"
#__pysys_modes__            = lambda helper: helper.inheritedModes + [ {'mode':'MyMode', 'myModeParam':123}, ]
#__pysys_parameterized_test_modes__ = {'MyParameterizedSubtestModeA':{'myModeParam':123}, 'MyParameterizedSubtestModeB':{'myModeParam':456}, }

import pysys
from pysys.constants import *
from apamax.analyticsbuilder.basetest import AnalyticsBuilderBaseTest

class PySysTest(AnalyticsBuilderBaseTest):
    def execute(self):
        correlator = self.startAnalyticsBuilderCorrelator(blockSourceDir=f'{self.project.root}/blocks/')
        modelId = self.createTestModel('com.example.analyticsbuilder.contrib.NormBlock', parameters={"threshold":0.01})
        self.sendEventStrings(correlator,
                              self.timestamp(1),
                              self.inputEvent('x', 0, id=modelId),
                              self.timestamp(2),
                              self.inputEvent('y', 1, id=modelId),
                              self.timestamp(2.1),
                              self.inputEvent('y', 2, id=modelId),
                              self.timestamp(3),
                              self.inputEvent('x', 0.01, id=modelId),
                              self.timestamp(3.1)
                              )
		
    def validate(self):
        self.assertGrep('correlator.log', '(ERROR|FATAL|WARN .*Failed to parse) .*', contains=False, ignores=[])
        self.assertBlockOutput('output', [0.0, 1.0, 2.0])
		
1 Like