# Update Module's Object Model
This tutorial will present a basic scenario where module will update its state in the cloud. Every "thing" in ABB Ability™ has its cloud representation in the form of information-model object - this includes our modules. Like any other information-model entity, our module can have: some variables, some methods, some properties, and so on. Anytime the information-model is updated, its version is incremented. Basically, such operation updates the current state that our device/module/thing is represented by in the cloud. Don't treat it interchangeably with what variables are used for - updating properties does not preserve the previous values of them. Another thing to remember is that the described way of updating information-models applies to Ability Edge modules themselves only. Updating modules' devices/children is done in a bit different way.
# Before you start
- Familiarize yourself with basic edge development concepts described in Standard Edge Module Operations
- Clone the sample code to your desired location.
- Download the .NET Core SDK
- Install favorite IDE or text editor
- Make sure you have the .NET Core CLI tools installed
# Overview
Our module is going to have a method called "ping" - the purpose of this method is to check if the module is online -
reachable module would return "200" HTTP status. If you look in properties section of the type definition, you will find
a property called pingCount
- the value of it should represent the amount of "ping" method invocations. Anytime
someone "pings" our module, its pingCount
in the information-model object should get updated.
# Method definition
"methods": {
"ping": {
"description": "Checks if the module is online",
"input": {}
}
}
As you can see, this simple method does not require any inputs.
# Property definition
"pingCount": {
"dataType": "number",
"value": 0
}
The property is of type number
and its value is initially equal to 0
for any new object.
# Update Module's Device Object Model
# Topic
Before looking at our specific case of updating pingCount
, let's look more generally at how Ability Edge enables such
procedure. First thing to note is the topic that should be used - it can be read from topics_model_out
environment
variable. If you intend to update different model that 'abb.ability.device' you should append it at the end of topic
name (prefixed with '/').
WARNING
As ABB Ability™ Edge documentation informs, entities themselves cannot update their 'abb.ability.configuration' models - that should to be done from the cloud level only.
In case of our tutorial, we'll be updating 'abb.ability.device' object model.
# Payload
Payload of the information-model update is exactly the same as the one we would use with Instance API's PUT endpoint of objects. It needs to contain information-model itself:
- objectId,
- model,
- type,
- properties (unique + the ones to be updated, if any).
Additional required parameter is the version from which we are updating. For example, if your current object has version 1, you'd append
(...)
"version" : 1
(...)
tag to your payload. As a result, the version would be bumped to 2. Here's an example of full payload:
{
"model": "abb.ability.device",
"type": "abb.ability.device.edge.modules.csharp.update.object.model.sample",
"properties": {
"name": {
"value": "1232121421"
},
"pingCount": {
"value": 7
}
},
"version": 1
}
The example payload above would update the pingCount
with value 7. It would be the first update since version
tag is at value 1.
# Handling "ping" Invocations
Anytime someone invokes "ping" command/method, our module should send the update request with its pingCount
property
value incremented. Let's look in details what steps are involved in this process.
# Receiving current state
Before we are able to update our information-model, we need to know our current state. This is possible thanks to
existence of topics_model_in
topic. This is the channel that delivers information-models to our module (of both the
module itself and its children/devices).
TIP
To learn more about topics_model_in
topic, read this article: Creating Configurable Edge
Modules
Every time your module starts (whether it is due to Edge booting up or due to your module restarting for any reason), it should receive its current information-model. Thanks to it, you are able to:
- read the current version of it,
- read the module's
name
property, - read the current value of
pingCount
.
All these information are necessary for our scenario to work. We cannot update information-model without knowing its current version. Additionally, we need to know the module's name since it is a unique property and each update requires its existence in the payload. The value of this property is partly randomly generated when information-model object is instantiated - that's why the module cannot know its value without information from the cloud.
More than that, we need to know current pingCount
not to lose the previous invocations of "ping" command (which could
get executed during previous runtime of our module, before Edge restart for example). This is why the following code is
needed:
private Task HandleIncomingInfoModelAsync(JObject infoModel)
{
_moduleDetails = ReadModuleDetails(infoModel);
var pingCountFromCloud = ReadPingCount(infoModel);
Console.WriteLine($"Received latest information-model version ({_moduleDetails.Version}) with pingCount = {pingCountFromCloud}");
_pingCountHandler.InternalPingCount = pingCountFromCloud + _unreportedPingCount;
if (_unreportedPingCount != 0)
{
Console.WriteLine($"There are {_unreportedPingCount} unreported pings. Info-model will be updated.");
_unreportedPingCount = 0;
return UpdateModelAsync();
}
return Task.CompletedTask;
}
private ModuleDetails ReadModuleDetails(JObject infoModel)
{
return new ModuleDetails(
infoModel["type"].Value<string>(),
infoModel["properties"]["name"]["value"].Value<string>(),
infoModel["version"].Value<int>());
}
ModuleDetails
is a simple POCO class (constructor was omitted, since it just assigns each value):
public class ModuleDetails
{
public ModuleDetails(string type, string name, int version) { ... }
public string Type { get; set; }
public string Name { get; set; }
public int Version { get; set; }
}
In the code, you can notice two things:
- usage of
_pingCountHandler
object, - existence of
_unreportedPingCount
field.
# PingCountHandler
The _pingCountHandler
object is an instance of PingCountHandler
class - its purpose is to handle ping count
operations:
- storing the current value of
pingCount
internally - updating
pingCount
in the cloud.
# Caching pingCount
PingCountHandler
stores internal value of pingCount
for the following reason: imagine that you are about to invoke
"ping" command 5 times - in order for the module to be able to update pingCount
in the cloud 5 times, it needs to know
the previous value of it, each time it updates it (to know the starting value before it increments it). We could fetch
this value from the cloud each time, however, it is not the best practice. It's much better, performance-wise, to cache
the latest pingCount
in the module.
# Updating model
The other responsibility of PingCountHandler
is updating the information-model. Here's the source code of this
functionality:
public Task UpdateCloudPingCountAsync(ModuleDetails moduleDetails)
{
if (InternalPingCount.HasValue)
{
Console.WriteLine("Updating the module's information-model");
var payload = new ModuleModelUpdatePayload(
_objectId,
moduleDetails.Version,
moduleDetails.Type,
new Dictionary<string, PropertyContent> {
{"pingCount", new PropertyContent(InternalPingCount) },
{"name", new PropertyContent(moduleDetails.Name)}
});
var payloadString = JsonConvert.SerializeObject(payload, GetSerializationSettings());
return _mqttClient.PublishAsync(_modelUpdateTopic, payloadString, MqttQualityOfServiceLevel.AtLeastOnce)
.ContinueWith((e) => Console.WriteLine($"Published message to topic '{_modelUpdateTopic}': {payloadString}"));
}
else
{
throw new InvalidOperationException("UpdateCloudPingCount was invoked when _pingCount was not initialized.");
}
}
As you can see, updating the information model comes down to publishing the latest version of it on the right topic.
# Unreported pings
It is important to cover every possible situation in your code. One of possibilities that could happen in our case is
the invocation of "ping" method before our module learns its current information-model details (version, current
pingCount
, name). In such situation, module is unable to update the information-model, simply because it lacks
necessary information. That's what _unreportedPingCount
is for. Value of it is incremented each time "ping" is invoked
and module is not yet aware of its details. When these details finally arrive, pingCount
is updated automatically
according to the number of unreportedPingCount
. The described situation would be probably very rare to happen
(invocation of method before information-model is delivered to the module), however it needs to be handled properly,
even if it's not much likely to occur.
# Running the Sample Module
There are two ways to run this custom module. CST provides types for a given version of Ability Platform. If your CST contact has not already loaded these types into your Ability environment, these types can be manually uploaded to the environment. Once the types are loaded into the instance, any supported Edge setup can be used to run the Edge types provided by the tutorials.
Once you are familiar with the Edge, it is possible to build custom versions of the tutorials. Once a custom modules is created and the type(s) are loaded into an instance, any valid Edge can run the custom type.
# Conclusion
This article dealt with updating module's 'abb.ability.device' information model. As was already stated in the introduction, make sure that you use that functionality in the right way and understand the difference between 'property' and 'variable' concepts of information modeling.