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?
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.
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)
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?
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;
}
}
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.
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”: