Tutorial overview

This tutorial demonstrates the use case how to develop an application for UCW Platform. The Smart Tracker is an example of how to implement the monitoring of moving objects for UCW Portal (source code). The tracker used in this example is Adafruit GPS FeatherWing micro-controller which employs the Global Positioning System (GPS) technology. Due to its high acceptability and accuracy rate, GPS has been applied in diverse areas such as transportation, agriculture, law enforcement, communication, military and weather forecasting. It uses a system of satellites to provide accurate time and location (position) of objects on the ground (earth). It is owned by the U.S government. Other navigation satellite systems are GLONASS, launched by Russia and Galileo positioning system, created by the European Union.

Requirements 

Step 01: Download tutorial sources

In order to download tutorial sources and test their functionality, do as follows:

  1. Make sure you have downloaded all the requirements above.
  2. Open a terminal/console and navigate to the directory where you want to work on the tutorial.
  3. Download the sources using following command:

    git clone https://github.com/unitycloudware/smart-tracker.git smart-tracker
  4. Move into newly created folder

    cd smart-tracker
  5. Test downloaded files by building and running the sources. In order to do so, execute following commands

    Unix / Linux based OS
    ./build.sh
    ./run-portal.sh

    or

    Windows
    build.bat
    run-portal.bat
  6. The application should be up and running on http://localhost:9602 (TODO - add link to setting tomcat port section). After clicking the link you should see the login page. If you see the login page you can proceed to the next step.

  7. You can login into the application using basic credentials (User: admin, Password: admin). (TODO - add link to credential settings)

Step 02: Dashboard

Data captured from the GPS sensor is displayed on a dashboard in a tabular and pictorial form. The framework for organising how the data will be displayed on a dashboard is handled by the nsys-plugin.xml file 

dashboard
<dashboard key="ucw-smarttracker_dashboard" name="UCW Smart Tracker Dashboard">
    <description>This dashboard displays data for geolocation coordinates from GPS modules.</description>
    <label>Smart Tracker</label>
    <viewId>ucw-smarttracker</viewId>
    <imageUri>${portalResourcesUrl}/resources/images/ucw_logo.png</imageUri>
    <actionButtons>ucw.smarttracker.dashboard.header.actions</actionButtons>
</dashboard>

Each dashboard is uniquely identified by a name and key

Step 03: Gadget Configuration

  1. Adding Geolocation data and Geolocation map gadgets to a dashboard using the nsys-plugin.xml file. 

    adding gadgets
    <dashboard-gadget key="ucw-smarttracker_geolocation-data-gadget" name="Geolocation Data Gadget" class="com.unitycloudware.portal.tutorial.smarttracker.gadget.GeolocationDataGadget">
        <description>Displays a table with geolocation coordinates from GPS modules.</description>
        <label>Geolocation Data</label>
        <column>left</column>
        <order>0</order>
        <view>ucw-smarttracker</view>
    </dashboard-gadget>
    
    <dashboard-gadget key="ucw-smarttracker_geolocation-map-gadget" name="Geolocation Map Gadget" class="com.unitycloudware.portal.tutorial.smarttracker.gadget.GeolocationMapGadget">
        <description>Displays a map with geolocation coordinates from GPS modules.</description>
        <label>Geolocation Map</label>
        <column>right</column>
        <order>0</order>
        <view>ucw-smarttracker</view>
    </dashboard-gadget>

    A dashboard consists of one or more gadgets. Each gadget has a unique name and key. All gadgets on this dashboard must have the same viewID value as the view of the dashboard
     

  2. GeolocationData model represents data that are sent from the device to the data stream ucw-smarttracker. It represents JSON in following structure 

    GeolocationData
    {"latitude":49.27398114208278,"longitude":-123.12387756842338,"timestamp":1520097514875}

    The gadget uses data model to display information about latitude, longitude and time stamp.
     

  3. Template file: The format in which data is displayed on a gadget is determined by a Velocity template (.vm file) using HTML. For Geolocation Data gadget, the data is displayed in a tabular format, while same data is represented as an image in Geolocation Map gadget. 

    geolocation-data.vm
    #parse ("macros.vm")
    
    #if ($!{data.isEmpty()})
        #showMessage("info", "Status", "No data to display.", false)
    #else
    <table class="aui aui-table-interactive aui-table-sortable">
        <thead>
        <tr>
            <th id="data-lattitude">Lattitude</th>
            <th id="data-longitude">Longitude</th>
            <th id="data-timestamp">Timestamp</th>
        </tr>
        </thead>
        <tbody>
            #foreach ($d in $data)
            <tr>
                <td headers="data-lattitude"><span class="aui-lozenge aui-lozenge-current">$String.format("%.2f", $!{d.lattitude})</span></td>
                <td headers="data-longitude"><span class="aui-lozenge aui-lozenge-current">$String.format("%.2f", $!{d.longitude})</span></td>
                <td headers="data-timestamp">$date.format("yyyy-MM-dd HH:mm:ss", $!{d.timestamp})</td>
            </tr>
            #end
        </tbody>
    </table>
    #end
    geolocation-map.vm
    $portalResourceManager.requireResourcesForContext("ucw.smarttracker.geolocation.map")
    
    <br/>
    <div id="location-map" class="location-map"></div>
    <script async defer
            src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB39lqCBWjMzIagsSuQ44e8OHFrRyw34is&callback=initLocationMap">
    </script>
    <br/>

    the class value "location-map" in the velocity template file above refers to .css that controls the dimension of the map 

    location-map
    .location-map {
       height: 500px;
       width: 100%;
       }
  4. Test Data Utils: The data stream received from sensors to the UCW platform are saved in projects. A  project can consist of different datastreams. Each data stream comprises of the deviceID and data (payload). If a project or any of the data stream components do not already exist, the TestDataUtils.java creates each one. It does this by first creating project manager, device manager and data manager 

    private ProjectManager projectManager;
    private DeviceManager deviceManager;
    private DataManager dataManager;
    
    public ProjectManager getProjectManager() {
       if (projectManager == null) {
          projectManager = ComponentProvider.getInstance().getComponent(ProjectManager.class);
         }
            return projectManager;
       }
    
    public DeviceManager getDeviceManager() {
       if (deviceManager == null) {
          deviceManager = ComponentProvider.getInstance().getComponent(DeviceManager.class);
          }
            return deviceManager;
       }
    
    public DataManager getDataManager() {
       if (dataManager == null) {
          dataManager = ComponentProvider.getInstance().getComponent(DataManager.class);
          }
            return dataManager;
        }

    then we proceed to create a project, device,and data

    protected void createProject() {
       log.debugFormat("Creating project '%s' with key '%s'...", PROJECT_NAME, PROJECT_KEY);
       if (getProjectManager().getProjectByKey(PROJECT_KEY) != null) {
          return;
          }
       Project project = new Project();
       project.setKey(PROJECT_KEY);
       project.setName(PROJECT_NAME);
       project.setDescription(PROJECT_DESC);
    
       if (getProjectManager().addProject(project) != null) {
          getProjectManager().configureProject(PROJECT_KEY);
          }
        }
    
    protected void createDevice() {
       log.debugFormat("Creating device '%s' for project '%s'...", DEVICE_NAME, PROJECT_KEY);
       Project project = getProjectManager().getProjectByKey(PROJECT_KEY);
       if (project == null || getDeviceManager().getDeviceByName(PROJECT_KEY, DEVICE_NAME) != null) {
          return;
          }
       Device device = getDeviceManager().createDevice(DEVICE_NAME, DEVICE_DESC, DeviceType.GENERIC.name(), 120, PROJECT_KEY);
       getDeviceManager().addDevice(device);
       }
    
    protected void createDataStream() {
       log.debugFormat("Creating data stream '%s' for project '%s'...", DATA_STREAM_NAME, PROJECT_KEY);
       Project project = getProjectManager().getProjectByKey(PROJECT_KEY);
       if (project == null || getDataManager().getDataStream(PROJECT_KEY, DATA_STREAM_NAME) != null) {
          return;
          }
       DataStream dataStream = new DataStream();
       dataStream.setName(DATA_STREAM_NAME);
       dataStream.setDescription(DATA_STREAM_DESC);
       dataStream.setProject(project);
       dataStream.setType(DataStreamType.DATA_MESSAGE);
       dataStream.setStorageType(StorageType.GENERIC.name());
       dataStream.setEnabled(true);
       getDataManager().addDataStream(dataStream);
       }
  5. SmartTrackerUtils.java converts the JSON payload received from the micro-controller into GeolocationData object

    toGeolocationData method
    public static GeolocationData toGeolocationData(final DataStreamItem item) {
       if (item == null || item.getType() != DataStreamType.DATA_MESSAGE) { // Process only data for data stream with type of DATA_MESSAGE
          return null;
          }
       DataMessage dataMessage = (DataMessage) item.getData();
       // Transform JSON payload to GeolocationData object
       GeolocationData geolocationData = JsonUtils.fromJson(dataMessage.getData(), GeolocationData.class);
       if (geolocationData.getTimestamp() == 0) {
          geolocationData.setTimestamp(dataMessage.getTimestamp());
          }
          return geolocationData;
        }

    and subsequently adds to data in datastream 

    public static List<GeolocationData> getData(final List<DataStreamItem> items) {
       if (items == null || items.isEmpty()) {
          return null;
          }
    List<GeolocationData> data = new ArrayList<GeolocationData>();
    for (DataStreamItem item : items) {
       GeolocationData geolocationData = toGeolocationData(item);
       if (geolocationData == null) {
          continue;
          }
       data.add(geolocationData);
       }
    return data;
  6. Gadget files: GeolocationDataGadget and GeolocationMapGadget perform the following tasks:
    a) set the velocity file as gadget template 

    Geolocation Data
    public static final String TEMPLATE = "/templates/gadget/geolocation-data.vm";
    public GeolocationDataGadget() {
       setTemplate(TEMPLATE);
       }
    Geolocation Map
    public static final String TEMPLATE = "/templates/gadget/geolocation-map.vm";
    public GeolocationMapGadget() {
       setTemplate(TEMPLATE);
       }

    b) Creates a Velocity context, called velocityParams. In the case of "Geolocation data" gadget, the velocity context takes two arguments: a string and list containing GeolocationData data. This is the data that is being read into the table of the Velocity file template. 

    Geolocation Data velocity context
    @Override
    protected Map<String, Object> createVelocityParams(final Map<String, Object> context) {
       Map<String, Object> velocityParams = new HashMap<String, Object>();
       velocityParams.put("data", getData());
       return velocityParams;
       }
    Geolocation Map velocity context
    @Override
    protected Map<String, Object> createVelocityParams(final Map<String, Object> context) {
       Map<String, Object> velocityParams = new HashMap<String, Object>();
       return velocityParams;
       }

Step 04: Sensor Data Generator 

SensorDataGeneratorJob.java file generates test data, in the absence of real sensor data, to be displayed on the dashboard. It achieves this with the aid of two methods:

  1. Each job has to implement method execute(final Map<String, Object> jobDataMap) that is the entry point of the job and is called every cycle when job runs. 

    execute(final Map<String, Object> jobDataMap)
    public void execute(final Map<String, Object> jobDataMap) {
       getLog().info("Executing sensor data generator...");
       if (!jobDataMap.containsKey(ComponentName.DATA_MANAGER)) {
          getLog().error("Unable to find DataManager component in jobDataMap!");
          return;
          }
       if (!jobDataMap.containsKey(ComponentName.TEST_DATA_UTILS)) {
          getLog().error("Unable to find TestDataUtils component in jobDataMap!");
          return;
          }
       setDataManager((DataManager) jobDataMap.get(ComponentName.DATA_MANAGER));
       setTestDataUtils((TestDataUtils) jobDataMap.get(ComponentName.TEST_DATA_UTILS));
       generateData();
       }


  2. generateData method generates random numbers for the longitude and latitude values. This is done by:

    a. obtain a deviceID and datastream using TestDataUtils.java

    b. search for data in datastream with deviceID. Use default location if no such data exists

    c. create several data values by adding randomly generated numbers between -0.005 to 0.005 to the longitude and latitude of the last/previous location ( this will be the default location for the second data or location) 

    generateData method
    public void generateData() {
       Device device = getTestDataUtils().getDevice();
       if (device == null) {
          return;
          }
       DataStream dataStream = getTestDataUtils().getDataStream();
       if (dataStream == null) {
          return;
          }
       GeolocationData data = null;
       if (getDataManager().countStream(dataStream, device) == 0) { // No data in data stream for device
          data = getTestDataUtils().getDefaultLocation();
          } else {
             List<DataStreamItem> items = getDataManager().loadStream(dataStream, device, 0, 1);
             if (items == null || items.isEmpty()) {
                return;
                }
       GeolocationData lastData = SmartTrackerUtils.toGeolocationData(items.get(0));
       int direction = RandomRange.getRandomInt(0, 1);
       if (direction == 1) {
          data = GeolocationData.create(lastData.getLatitude(),lastData.getLongitude() + RandomRange.getRandomDouble(-0.005, 0.005),TimeUtils.getNow().getTime());
                } else {
                    data = GeolocationData.create(lastData.getLatitude() + RandomRange.getRandomDouble(-0.005, 0.005),lastData.getLongitude(),TimeUtils.getNow().getTime());
                }
          }
       String payload = JsonUtils.toJson(data);
       getLog().debugFormat("Storing generated sensor data... Payload: %s", payload);
       getDataManager().storeStream(dataStream, device, payload);
       }

Step 05: UCW Portal Plugin

The SmartTrackerPlugin.java file handles the job scheduling of the SensorDataGeneratorJob.java. 

  1. Method scheduleJobs() creates a HashMap pairing a device manager and data manager with device manager and data manager objects respectively 

    scheduleJobs()
    protected void scheduleJobs() {
       // When the sensor data generator is disabled then skip adding job to scheduler.
       if (!SmartTrackerConfig.isGeneratorEnabled()) {
          return;
          }
       SchedulerService scheduler = ServiceProvider.getInstance().getServiceHost(SchedulerService.class);
       //Date delay1min = TimeUtils.addMinutes(TimeUtils.getNow(), 1);
       //long repeatInterval = (600 * 1000) * 2; // 2mins
       Date delay30sec = TimeUtils.addSeconds(TimeUtils.getNow(), 30);
       long repeatInterval = 10 * 1000; // 10sec
       Map<String, Object> jobDataMap = new HashMap<String, Object>();
       jobDataMap.put(ComponentName.DATA_MANAGER, ComponentProvider.getInstance().getComponent(DataManager.class));
       jobDataMap.put(ComponentName.TEST_DATA_UTILS, ComponentProvider.getInstance().getComponent(TestDataUtils.class));
       scheduler.scheduleJob(SensorDataGeneratorJob.class, jobDataMap, delay30sec, repeatInterval);
       }
  2. Method createTestData() creates a project, device and data stream (if non already exists) using the createData() method from TestDataUtils.java 

    createTestData()
    protected void createTestData() {
       TestDataUtils testDataUtils = ComponentProvider.getInstance().getComponent(TestDataUtils.class);
       testDataUtils.createData();
       }

Result

You can build and run the application and the resulting page should look like this

  • No labels