# Object Model Endpoints

It is time to get back to our SPA. In this part of the tutorial we will:

  • extend the SPA with the functionality of GETting object models
  • extend the SPA with the functionality of PATCHing object models

Make sure that your Edge module is still running, since the second point of the list above will be reflected in the runtime of the module.

# Object Model Service

First, we will add a new service that will be responsible for handling object model related requests. If you stopped our web application, let's get it back into running state by invoking the following, well-known to you, command (in the directory of the app):

npm run serve

Open the code editor and add a new file src/objectModels/objectModelService.js (a new directory objectModels is needed):

import axios from 'axios';

async function getObjectModel(accessToken, objectId, modelDefinition) {
  const url = new URL(
    `v1/objects/${objectId}/models/${modelDefinition}`,
    process.env.VUE_APP_INSTANCE_API_URL
  )

  try {
    const response = await axios({
      method: "get",
      url: url.toString(),
      responseType: "json",
      headers: { Authorization: `Bearer ${accessToken}` },
    })

    return response.data
  }
  catch (error) {
    console.log(error.response)
    throw (error)
  }

}

async function patchObjectModel(accessToken, objectId, modelDefinition, updateBody) {
  const url = new URL(
    `v1/objects/${objectId}/models/${modelDefinition}`,
    process.env.VUE_APP_INSTANCE_API_URL
  )

  try {
    const response = await axios({
      method: "patch",
      url: url.toString(),
      responseType: "json",
      headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
      data: updateBody
    })
    return response.data
  } catch (error) {
    console.log(error.response)
    throw (error)
  }
}

export { getObjectModel, patchObjectModel }

Both functions are rather simple. Just like before, we're making use of axios to simplify the HTTP request operations.

# Reading Object Models

Since we have a function getObjectModel defined, we can use it in our app. Let's create a new Vue.js component src/objectModels/ObjectModelGet.vue:

<template>
  <div class="card" style="height: 100%">
    <div class="card-body">
      <h5 class="card-title">3. Object Model Retrieval</h5>
      <h6 class="card-subtitle mb-4 text-muted">You can get the 
      object models that were created by the Ability Edge framework</h6>
      <div class="container">
        <div class="row">
          <form @submit.prevent class="col">

            <div class="form-group">
              <label for="objectIdInput">ObjectId</label>
              <input 
                v-model="objectId" 
                type="text" 
                class="form-control" 
                id="objectIdInput"
                placeholder="00000000-0000-0000-0000-000000000000" />
              <small class="form-text text-muted">The objectId of the 
              object that you want to retrieve.</small>
            </div>

            <div class="form-group">
              <label for="modelDefinitionInput1">Model Definition</label>
              <input
                v-model="modelDefinition"
                type="text"
                class="form-control"
                id="modelDefinitionInput1"
              />
              <small class="form-text text-muted">The model definition of the object.</small>
            </div>

            <button
              type="button"
              @click="getObject"
              class="btn btn-primary mb-3"
              :disabled="!objectId || !modelDefinition"
            >GET</button>
          </form>
        </div>

        <div class="row">
          <div class="col">
            <label for="objectGetTextArea">Response</label>
            <textarea id="objectGetTextArea" readonly 
            v-model="result" class="form-control" rows="9"></textarea>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import AuthService from "../auth/AuthService";
import { getObjectModel } from './objectModelService'

export default {
  name: "ObjectModelGet",
  data: function () {
    return {
      authService: null,
      objectId: null,
      modelDefinition: "abb.ability.device",
      result: null
    };
  },
  methods: {
    async getObject() {
      try {
        this.result = null

        if (!this.authService.isAuthenticated()) {
          await this.authService.signIn();
        }
        const { accessToken } = await this.authService.getToken();

        const data = await getObjectModel(accessToken, this.objectId, this.modelDefinition);
        this.result = JSON.stringify(data, null, 2);
      }
      catch (error) {
        console.log(error)
        this.result = error;
      }

    },
  },
  created: function () {
    this.authService = AuthService.getInstance();
  },
};
</script>

If you followed the previous parts of the tutorial, there shouldn't be any surprises for you. The APi requires the model definition and objectId to be provided. We have the inputs for these. In the JavaScript portion we retrieve an access token and invoke our service that we just created.

Let's now update src/App.vue in order to display the new component:

















 

















 






 




<template>
  <div class="app">
    <header class="mb-4">
      <nav class="navbar navbar-light bg-white border-bottom">
        <div class="container">
            <span class="navbar-brand mb-0 h1 text-wrap">
            Getting Started with <span class="abb font-weight-bold">ABB Ability™ Platform
            </span></span>
        </div>
      </nav>
    </header>

    <main class="container">
      <div class="row">
        <div class="col-12 mb-4"><AccessToken /></div>
        <div class="col-lg-6 mb-4"><TypeDefinition /></div>
        <div class="col-lg-6 mb-4"><ObjectModelGet /></div>
      </div>
    </main>

    <footer class="footer mt-4 p-3 bg-white border-top">
      <div class="container">
        <span class="text-muted">This application is part of the Ability Getting Started 
        tutorial series that can be found on 
        the <a target="_blank" href="https://clientsuccess.ability.abb">
        Ability Developer Portal</a>.</span>
      </div>
    </footer>
  </div>
</template>

<script>
import AccessToken from './auth/AccessToken';
import TypeDefinition from './typeDefinitions/TypeDefinition';
import ObjectModelGet from './objectModels/ObjectModelGet';

export default {
  name: 'App',
  components: {
    AccessToken,
    TypeDefinition,
    ObjectModelGet
  }
}
</script>

We imported our new compnent, declared it in the components object and displayed in the HTML template.

The browser should be already displaying it:

Reading Object Model module

You can test it out by providing some objectId (i.e. the objectId of your module that can be found in the logs on the Edge) and corresponding model definition name (for module it could be either "abb.ability.device" or "abb.ability.configuration").

You are not going to see anything new to you - you have seen this models in the logs of your module already. However, you learnt about an important API endpoint of the platform!

TIP

Other than GETting the selected object model, API allows you also to create, update, patch (as we will see in a moment), delete, query, and others. You can see all available endpoints in API Playground.

# Patching Object Models

Let's move on to a more interested part of this article - patching object models. Our module.configuration type has a property called telemetryInterval. The whole point of creating abb.ability.configuration based type definitions is to provide a way of configuring your entities. Our Edge module is subscribed to its configuration changes. Let's see if this functionality actually works.

TIP

It wil be beneficial if you have your Edge still running. It would be best to display the logs of your module in a "follow" mode. First list your services with docker service ls, make a note of your module's service ID, and then invoke docker service logs -f <Id_of_your_module_service> with the ID in place of the placeholder.

First, let's create another Vue.js component - src/objectModels/ObjectModelPatch:

<template>
  <div class="card" style="height: 100%">
    <div class="card-body">
      <h5 class="card-title">4. Object Model Patch</h5>
      <h6
        class="card-subtitle text-muted mb-4"
      >You can update your objects, i.e. the configuration of your Edge module</h6>
      <div class="container">
        <div class="row">
          <div class="col">
            <form @submit.prevent>
              <div class="form-group">
                <label for="objectIdInput2">ObjectId</label>
                <input
                  v-model="objectId"
                  type="text"
                  class="form-control"
                  id="objectIdInput2"
                  placeholder="00000000-0000-0000-0000-000000000000"
                />
                <small
                  class="form-text text-muted"
                >The objectId of the object that should be updated.</small>
              </div>

              <div class="form-group">
                <label for="modelDefinitionInput2">Model Definition</label>
                <input
                  v-model="modelDefinition"
                  type="text"
                  class="form-control"
                  id="modelDefinitionInput2"
                />
                <small class="form-text text-muted">The model definition of the object.</small>
              </div>

              <div class="form-group">
                <label for="objectUpdateTextArea">Object Model Update</label>
                <textarea
                  v-model="objectUpdateBody"
                  class="form-control"
                  id="objectUpdateTextArea"
                  rows="6"
                />
                <small class="form-text text-muted">The part of your object to be updated.</small>
              </div>

              <button
                type="button"
                @click="updateObject"
                class="btn btn-primary mb-3"
                :disabled="!modelDefinition || !objectId"
              >PATCH</button>

              <section
                class="alert"
                v-if="isSuccess !== null"
                v-bind:class="{ 'alert-success': isSuccess, 'alert-danger': isSuccess === false }"
              >{{ alertText }}</section>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import AuthService from "../auth/AuthService";
import { patchObjectModel } from "./objectModelService";

export default {
  name: "ObjectModelPatch",
  data: function () {
    return {
      authService: null,
      modelDefinition: "abb.ability.configuration",
      objectId: null,
      objectUpdateBody: null,
      isSuccess: null,
      alertText: null,
    };
  },
  methods: {
    async updateObject() {
      try {
        this.isSuccess = null
        this.alertText = null

        if (!this.authService.isAuthenticated()) {
          await this.authService.signIn();
        }
        const { accessToken } = await this.authService.getToken();

        const response = await patchObjectModel(
          accessToken,
          this.objectId,
          this.modelDefinition,
          this.objectUpdateBody
        );
        this.alertText = "Object updated.";
        this.isSuccess = true;
      } catch (error) {
        console.log(error);
        this.alertText =
          "There was some problem with your request. Check the console for details.";
        this.isSuccess = false;
      }
    },
  },
  created: function () {
    this.authService = AuthService.getInstance();
  },
};
</script>

The code is very similar to our previous component (ObjectModelGet). The only differences are a different set of inputs (this time we used textarea, because PATCH endpoint requires a payload). We also use the patchObjectModel function that we recently created in objectModelService.js.

As usually, let's make this component visible on the page by adding it into src/App.vue:


















 


















 







 




<template>
  <div class="app">
    <header class="mb-4">
      <nav class="navbar navbar-light bg-white border-bottom">
        <div class="container">
            <span class="navbar-brand mb-0 h1 text-wrap">
            Getting Started with <span class="abb font-weight-bold">ABB Ability™ Platform
            </span></span>
        </div>
      </nav>
    </header>

    <main class="container">
      <div class="row">
        <div class="col-12 mb-4"><AccessToken /></div>
        <div class="col-lg-6 mb-4"><TypeDefinition /></div>
        <div class="col-lg-6 mb-4"><ObjectModelGet /></div>
        <div class="col-lg-6 mb-4"><ObjectModelPatch /></div>
      </div>
    </main>

    <footer class="footer mt-4 p-3 bg-white border-top">
      <div class="container">
        <span class="text-muted">This application is part of the Ability Getting Started 
        tutorial series that can be found on 
        the <a target="_blank" href="https://clientsuccess.ability.abb">
        Ability Developer Portal</a>.</span>
      </div>
    </footer>
  </div>
</template>

<script>
import AccessToken from './auth/AccessToken';
import TypeDefinition from './typeDefinitions/TypeDefinition';
import ObjectModelGet from './objectModels/ObjectModelGet';
import ObjectModelPatch from './objectModels/ObjectModelPatch';

export default {
  name: 'App',
  components: {
    AccessToken,
    TypeDefinition,
    ObjectModelGet,
    ObjectModelPatch
  }
}
</script>

The result looks like that:

Object Patch in SPA

Let's try to use it. Firstly, let's use the previous component to get the abb.ability.configuration object of our module:

Configuration of the module

We will try to update the value of its telemetryInterval property. Currently it should be equal to 5.

In order to update it, we will send the following payload in the PATCH:

{
  "properties": {
    "telemetryInterval": {
      "value": 3
    }
  }
}

The value can be any integer ("integer" data type has been specified in the type definition, so we have to follow that - other kind of value would result in an error being returned).

You also need to provide the objectId and model definition (abb.ability.configuration in this case) of your module.

Patching module's configuration

TIP

There is also a PUT endpoint that has a similar functionality as the PATCH one. The difference is that in the PUT one, you have to provide the whole object that you are updating, not just the part that you're changing. It's recommended to use PATCH when possible, since the sent payload may be much smaller in some cases (you could have dozens of properties in your object).

If your Edge is still running, you will see a new log appear (according to the code of the module that we created):

Edge logs reporting a new value of telemetry interval

This way we achieved cloud-to-device communication.

WARNING

You just patched a value of telemetryInterval of our module's configuration. Note that we did it only for this one instance of the module! The type definition still contains the original value (5). If you decided to spin up a new Edge, another set of objects will be created for it and the telemetryInterval will be equal to 5 there. If you wanted to change the value for every future instance of your module that ever gets creates, you'd need to update the type definition of it with the new value, and not just some single object.

# Summary

In this part of the tutorial we have finally "connected" our web application with the Edge, in a way that an action made in the web app causes a reaction on the Edge. Let's move further with that by enabling/disabling timeseries telemetry on the Edge via the web app.

Author: Marcin Jahn
Last updated: 1/10/2022, 11:05:26 AM
Feedback