How to add an offset to a sensor value

Product/components used and version/fix level:

  • Frontend 1018.0.170
  • Backend 1018.0.261

Detailed explanation of the problem:

We use various sensors that measure the water level in wells or tanks. Some Lorawan sensors (data would be transmitted via TTN) and some NB-IOT sensors which send directly to the Cumulocity Platform via MQTT. Since these sensors are used on construction sites and are often moved, we are looking for a simple way to add an offset to correctly record the measurement.

Example: The sensor hangs in a tank that is 15m deep only up to 11.40m. An offset of 3.60m must be added here.

An offset must be assigned separately for each sensor and, if possible, as simply as possible.

Many thanks for the support.

Best regards

Mirko

Question related to a free trial, or to a production (customer) instance?

this is a production licence

Hi Mirko,

you could add a property that holds the current offset to the Devices Managed Object and use that value in a Streaming Analytics Rule (Analytic Builder or EPL-Apps) to create an additional offset corrected measurement.

Kind Regards,

Marco

1 Like

Attached an example of an Analytics Builder Model. You can add the offset property under Administration → Settings → Properties Library. Use an Assets Properties Widget in Cockpit to enter the Offset.
Add Offset.json.zip (2.0 KB)

1 Like

Hi Marco,

it seems its working now. Thank you very much!

1 Like

Servus Marco,

There is still an important detail that I need to fix.

The offset works fine but only after I have set an offset (value) in the Assets Properties Widget AFTER the rule has been set to ACTIVE. As soon as the rule is stopped and started again, the offset hangs. Is there some kind of INIT after the restart? What happens if at some point 100 devices with different offsets are registered the service restarts? after am update for example?

Thank you and nice weekend.

Mirko

Hi Mirko,

I have created sample EPL App that you can use as a starting point. The approach is much more generic. You can define a filter for type fragment and series of a measurement to match where to add the offset. The app will pick up new ManagedObjects that have the offset property on runtime.

Just have a look at the code. you can paste that directly into EPL Apps:

/**
* This application adds an offset to each measurement that matches the following parameters 
* seriesOffset
* fragmentOffset
* typeOffset
* The offset has to be configured in the managed object as a property named "offset"
*/

using com.apama.cumulocity.ManagedObject;
using com.apama.cumulocity.FindManagedObject;
using com.apama.cumulocity.FindManagedObjectResponse;
using com.apama.cumulocity.FindManagedObjectResponseAck;
using com.apama.cumulocity.MeasurementFragment;
using com.apama.cumulocity.Util;

monitor Offset {
	
	dictionary <string, float> offMOs := new dictionary<string, float>;
	string seriesOffset := "accelerationZ";
	string fragmentOffset := "c8y_Acceleration";
	string typeOffset:= "c8y_Acceleration";
	string suffix := "_offset";
	
	action onload() {

		monitor.subscribe(MeasurementFragment.SUBSCRIBE_CHANNEL);
		monitor.subscribe(ManagedObject.SUBSCRIBE_CHANNEL);

		searchOffSetMOs();
		
		/**
		* Check each measurement if it fits the filter create a new measurement with offset addition.
		**/
		on all MeasurementFragment(type=typeOffset) as mfo {
			if (
					mfo.valueFragment = fragmentOffset and 
					mfo.valueSeries = seriesOffset and
					offMOs.hasKey(mfo.source)
					){
				float offsetValue := mfo.value + offMOs.getOrAdd(mfo.source, 0.0);
				MeasurementFragment mf := new MeasurementFragment;
				mf.type := mfo.type;
				mf.source := mfo.source;
				mf.time := mfo.time;
				mf.valueFragment := mfo.valueFragment;
				mf.valueSeries := mfo.valueSeries + suffix;
				mf.value := offsetValue;
				mf.unit := mfo.unit;
				log "Sendign offset measurementFragment: " + mf.toString() at INFO;
        		send mf to MeasurementFragment.SEND_CHANNEL;
			}	
		}

		/**
		* On each change of a ManagedObject check if it still has the offset fragment. 
		* If no remove from dict. If yes update dict.
		**/
		on all ManagedObject() as mo {
			
			log "MO Change: " + mo.toString() at INFO;
			if mo.params.hasKey("offset"){
				offMOs.add(mo.id, <float> mo.params.getOr("offset", 0.0));
			}else{
				if offMOs.hasKey(mo.id){
					offMOs.remove(mo.id);
				}
			}
			log "Updated offMOs: " + offMOs.toString() at INFO;
		}
	}
	
	
	/**
	* Search all ManagedObjects that have the offset fragment 
	* and save the id an offset in a dictionary 
	**/
	action searchOffSetMOs(){
		offMOs.clear();
		FindManagedObject findManagedObject := new FindManagedObject;
		findManagedObject.reqId := Util.generateReqId();

		/* Filter on managed objects with wich match devices and has offset fragment */
		findManagedObject.params.add("query", "$filter=( has(c8y_IsDevice) and has(offset) )");

		/* Retrieve only the first 2000 managed objects. */
		findManagedObject.params.add("currentPage", "1");
		findManagedObject.params.add("pageSize", "2000");

		/** Subscribe to FindManagedObjectResponse.SUBSCRIBE_CHANNEL to 
		 *  listen for responses.
		 */
		monitor.subscribe(FindManagedObjectResponse.SUBSCRIBE_CHANNEL);

		/** Listen for matching responses. */
		on all FindManagedObjectResponse(reqId=findManagedObject.reqId) as resp
		and not FindManagedObjectResponseAck(reqId=findManagedObject.reqId) {
			log "Find managed object response : " +
									resp.managedObject.toString() at INFO;
			offMOs.add(resp.managedObject.id, <float> resp.managedObject.params.getOr("offset",0.0));
			log "Found Ids: " + offMOs.toString() at INFO;
		}

		/** Listen for request completed acknowledgement. */
		on FindManagedObjectResponseAck(reqId=findManagedObject.reqId) {
			monitor.unsubscribe(FindManagedObjectResponse.SUBSCRIBE_CHANNEL);
			log "Find managed object request completed " +
									findManagedObject.reqId.toString() at INFO;
			
		}

		send findManagedObject to FindManagedObject.SEND_CHANNEL;
	}
}

Kind Regards,

Marco

2 Likes

Hi Marco,

thank you very much. Unfortunately our Cumulocity do not have the EPL feature. I need to talk to the support to activate that feature. I will come back to you as soon as we have it.

Mirko

Hi Mirko,

while @Marco_Stoffel 's second solution is more generic, you should be fine with his first solution with a small modification. Enable “Capture Start Value”:

That way, if the model is restarted for whatever reason, the current value is read without you having to change it.

Hello Harald,
Enable “Capture Start Value”!

this is the Solution! Thank you!

1 Like