Automated GPX Route Generation for IoT Service Teams

Device Operation on a Field Level

By their nature, some devices run only on battery power. Here’s an example of a solution we built for a customer to help their field workers identify and replace batteries in devices that need attention.

First Step

The first step is to create your Data Point to define the thresholds for when the alarm should be created.

After that, I created a Smart Rule named:
“Creates alarms when Battery Level reaches a critical level”

I activated it on the assets or groups you would like to monitor.

Next, I use the Analytics Builder to update a fragment on the device’s managed object, which lets me later detect whether the battery needs replacing.

Here is the JSON source code for this model:

{
    "id": "55145597918",
    "templateParameters": [
        {
            "name": "Alarm type",
            "id": "MP_0ls8aleid",
            "defaultValue": "c8y_BatteryLevel",
            "typeDisplayName": {
                "typeName": "string"
            },
            "type": "string",
            "optional": false
        },
        {
            "semanticType": "c8y_deviceOrGroupId",
            "id": "MP_afkf0mxoa",
            "typeDisplayName": {
                "semanticType": "c8y_deviceOrGroupId",
                "typeName": "c8y_deviceOrGroupId"
            },
            "autoSelect": true,
            "name": "Device or group of devices",
            "type": "string",
            "sourceOrDestinationType": [
                "Device",
                "Groups",
                "Assets"
            ],
            "optional": false
        },
        {
            "name": "fragmentToBeUsed",
            "id": "MP_icnx20sc5",
            "defaultValue": "batteryLow",
            "typeDisplayName": {
                "typeName": "string"
            },
            "type": "string",
            "description": "",
            "optional": false
        },
        {
            "name": "valueForFragmentOK",
            "id": "MP_kriq685iz",
            "defaultValue": "OK",
            "typeDisplayName": {
                "typeName": "string"
            },
            "type": "string",
            "description": "",
            "optional": false
        },
        {
            "name": "valueForFragmentNOK",
            "id": "MP_7i4d47iev",
            "defaultValue": "NOK",
            "typeDisplayName": {
                "typeName": "string"
            },
            "type": "string",
            "description": "",
            "optional": false
        }
    ],
    "tags": [
        "Alarm",
        "Smart rule"
    ],
    "builderModel": {
        "nodeDataArray": [
            {
                "id": "apama.analyticskit.blocks.cumulocity.AlarmInput",
                "key": -1,
                "type": "C8YDevice Block",
                "location": "-192 96",
                "blockType": "c8y_Input",
                "parameters": [
                    {
                        "id": "alarmType",
                        "value": {
                            "id": "MP_0ls8aleid",
                            "type": "TemplateParameterReference"
                        }
                    },
                    {
                        "id": "deviceId",
                        "c8y_type": "c8y_deviceOrGroupId",
                        "value": {
                            "id": "MP_afkf0mxoa",
                            "type": "TemplateParameterReference"
                        }
                    },
                    {
                        "id": "alarmStatus",
                        "value": "ACTIVE"
                    },
                    {
                        "id": "notificationMode",
                        "value": "ALL"
                    },
                    {
                        "id": "ignoreTimestamp",
                        "value": false
                    }
                ],
                "categoryId": "Input"
            },
            {
                "id": "apama.analyticskit.blocks.cumulocity.ManagedObjectOutput",
                "key": -8,
                "type": "C8YDevice Block",
                "location": "352 72",
                "blockType": "c8y_Output",
                "parameters": [
                    {
                        "id": "deviceId",
                        "c8y_type": "c8y_deviceIdOrCurrentDevice",
                        "value": {
                            "currentDevice": true
                        }
                    },
                    {
                        "id": "propertyName",
                        "value": {
                            "id": "MP_icnx20sc5",
                            "type": "TemplateParameterReference"
                        }
                    }
                ],
                "categoryId": "Output"
            },
            {
                "id": "apama.analyticskit.blocks.cumulocity.ManagedObjectOutput",
                "key": -11,
                "type": "C8YDevice Block",
                "location": "352 416",
                "blockType": "c8y_Output",
                "parameters": [
                    {
                        "id": "deviceId",
                        "c8y_type": "c8y_deviceIdOrCurrentDevice",
                        "value": {
                            "currentDevice": true
                        }
                    },
                    {
                        "id": "propertyName",
                        "value": {
                            "id": "MP_icnx20sc5",
                            "type": "TemplateParameterReference"
                        }
                    }
                ],
                "categoryId": "Output"
            },
            {
                "id": "apama.analyticsbuilder.blocks.ConstantValue",
                "key": -12,
                "type": "Analytics Block",
                "location": "-192 -8",
                "categoryId": "Utilities",
                "parameters": [
                    {
                        "id": "value",
                        "c8y_type": "pab_multiLine",
                        "value": {
                            "id": "MP_7i4d47iev",
                            "type": "TemplateParameterReference"
                        }
                    },
                    {
                        "id": "type",
                        "value": "string"
                    }
                ]
            },
            {
                "id": "apama.analyticsbuilder.blocks.ConstantValue",
                "key": -13,
                "type": "Analytics Block",
                "location": "-200 328",
                "categoryId": "Utilities",
                "parameters": [
                    {
                        "id": "value",
                        "c8y_type": "pab_multiLine",
                        "value": {
                            "id": "MP_kriq685iz",
                            "type": "TemplateParameterReference"
                        }
                    },
                    {
                        "id": "type",
                        "value": "string"
                    }
                ]
            },
            {
                "id": "apama.analyticskit.blocks.cumulocity.AlarmInput",
                "key": -15,
                "type": "C8YDevice Block",
                "location": "-208 440",
                "blockType": "c8y_Input",
                "parameters": [
                    {
                        "id": "alarmType",
                        "value": {
                            "id": "MP_0ls8aleid",
                            "type": "TemplateParameterReference"
                        }
                    },
                    {
                        "id": "deviceId",
                        "c8y_type": "c8y_deviceOrGroupId",
                        "value": {
                            "id": "MP_afkf0mxoa",
                            "type": "TemplateParameterReference"
                        }
                    },
                    {
                        "id": "alarmStatus",
                        "value": "CLEARED"
                    },
                    {
                        "id": "notificationMode",
                        "value": "ALL"
                    },
                    {
                        "id": "ignoreTimestamp",
                        "value": false
                    }
                ],
                "categoryId": "Input"
            }
        ],
        "linkDataArray": [
            {
                "outputId": "value",
                "to": -11,
                "from": -13,
                "inputId": "output",
                "points": [
                    -112,
                    328,
                    -97,
                    328,
                    100,
                    328,
                    100,
                    392,
                    249,
                    392,
                    264,
                    392
                ]
            },
            {
                "outputId": "updateProperty",
                "to": -11,
                "from": -15,
                "inputId": "value",
                "points": [
                    -120,
                    440,
                    -105,
                    440,
                    88,
                    440,
                    88,
                    440,
                    249,
                    440,
                    264,
                    440
                ]
            },
            {
                "outputId": "updateProperty",
                "to": -8,
                "from": -1,
                "inputId": "value",
                "points": [
                    -104,
                    96,
                    -89,
                    96,
                    80,
                    96,
                    80,
                    96,
                    249,
                    96,
                    264,
                    96
                ]
            },
            {
                "outputId": "value",
                "to": -8,
                "from": -12,
                "inputId": "output",
                "points": [
                    -104,
                    -8,
                    -89,
                    -8,
                    76,
                    -8,
                    76,
                    48,
                    249,
                    48,
                    264,
                    48
                ]
            }
        ]
    },
    "c8y_ActiveAlarmsStatus": {
        "warning": 1
    },
    "name": "Update fragment based on an incoming Alarm",
    "type": "c8y_PublicAnalyticsBuilderModel",
    "c8y_analyticsModel": {},
    "userInterfaceProperties": {
        "displayGrid": true
    },
    "description": "Update fragment based on an incoming Alarm"
}

In my example, I created a fragment named batteryLevel.

Using our handy CLI tool, goc8y-cli, I added a new fragment to each device:

c8y devices list --includeAll | c8y inventory update --template "{batteryLevel:'OK'}"

…assuming all batteries are fine :wink:.

As a result, if a new alarm comes in, e.g.:

c8y alarms create --device 397735 --type c8y_BatteryLevel --time "-0s" --text "Test alarm" --severity MAJOR

The fragment on the device changes via the Analytics Builder (AB) from OK to NOK. So far so good — but how does this help the field workers?

Second Step

The requirement was simple and clear: “If I have the GPS coordinates of all devices with depleted batteries, please create routing information for my field workers in Google Maps.”

Using our HTML Widget (Enhanced HTML Widget: Revolutionizing Dashboard Development with AI-Powered Generation) and its AI-powered generation functionality, I created a widget that does the following:

  • Lets you configure a start and end point for the route
  • Searches for assets whose related devices have a batteryLevel fragment set to NOK, and extracts the GPS coordinates of those assets (in my case, GPS coordinates belong to the asset, not the device itself)
  • Calculates the fastest route with OSRM (Open Source Routing Machine)
  • Splits the coordinates into batches of a maximum of 20 waypoints per route
  • Provides one or more GPX files for these routes, ready to use in Google Maps

The widget’s source code is available from @Kai_Reinhardt.

1 Like