View on GitHub

qiot-fr-fr-utf8.github.io

Documentation for Hackfest QIoT project.

QIoT Edge Service

How it works

Class Diagram

Pulling from qiot Sensor Service

We have implemented this interface to gather data from our python Rest API services. This interface will be call every 5s to send data to DataHub.

package fr.axians.qiot.edge_service.service.sensor;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@Path("/sensors")
@RegisterRestClient
public interface SensorService {
    
    @GET
    @Path("/gas")
    @Produces("application/json")
    Result getGasResult();

    @GET
    @Path("/pollution")
    @Produces("application/json")
    Result getPollutionResult();
}

Scheduling

The scheduling is done by TelemetryService. We get data from Sensor Service and put it on DataHub. We choose to use the Flowable object to create our stream between our qiot edge services and the DataHub. This object associated with our MQTT parameter set in our resource files allow us to send a String that contains our air quality information.

public class TelemetryService {
    
    private static final Logger LOGGER = Logger.getLogger("ListenerBean");
    @Inject
    SensorResource sr;

    
    /* Gas stream */
    @Outgoing("gas-stream")
    public Flowable <String> streamGasData() throws JsonProcessingException {
        
        /* Creating the ObjectMapper object */
        ObjectMapper mapper = new ObjectMapper();
        LOGGER.info(mapper.writeValueAsString(sr.getGas()));
        return Flowable.interval(5, TimeUnit.SECONDS).map(interval -> mapper.writeValueAsString(sr.getGas()));
    }


    /* Pollution stream */
    @Outgoing("pollution-stream")
    public Flowable <String> streamPollutionData() throws JsonProcessingException {
        /* Creating the ObjectMapper object */
        ObjectMapper mapper = new ObjectMapper();
        LOGGER.info(mapper.writeValueAsString(sr.getPollution()));
        /* Converting the Gas object to JSONString */
        return Flowable.interval(5, TimeUnit.SECONDS).map(interval ->  mapper.writeValueAsString(sr.getPollution()));
    }
}

Shutdown

When we gracefully shutdown the QIoT Edge Service, we are calling a function (more information bellow) that sends a request to unsubscribe.

void onStop(@Observes ShutdownEvent ev) {
        LOGGER.info("Stop application begin uregistering process ...");               
        regService.unregStation(this.st.getId());
    }

init overload

Here we have our initialization class that instantiated dynamically configuration parameters we need.

void init(){
        this.st = new Station();
        LOGGER.info(this.st.toString());
        
        //Defined the machine-id path to be able to regester
        java.nio.file.Path  path = Paths.get("/etc/machine-id");
        String content = null;

        //Get the id defined in the file
        try {
            content = Files.readString(path, StandardCharsets.UTF_8);
        } catch (IOException e) {
            LOGGER.error(e);
        };

        //Initialize the Station information
        this.st.setSerial(content);
        LOGGER.info("Device name : "+this.st.getName());

        /* Retrieve stationId and activate the station */
        Integer stationId =0;
        stationId = regService.regStation(this.st.getSerial(), this.st.getName(), this.st.getLongitude(), this.st.getLatitude());
        this.st.setId(stationId);
        this.st.setActive(true);
        LOGGER.info("Register ID : "+this.st.getId());
    }
  1. Get the unique machine ID
  2. Register the Device on DataHub

It’s necessary to use the /etc/machine-id of Host machine and not ephemeral container. To do this, we share the host file with containers at the creation:

podman -v /etc/machine-id:/etc/machine-id:ro […]

(Un)Register

We have created an Interface to Register and UnRegister Device on DataHub:

public interface RegistrationService {

    /* Registration of the station */
    @PUT
    @Path("/register/serial/{serial}/name/{name}/longitude/{longitude}/latitude/{latitude}")
    @Produces(MediaType.TEXT_PLAIN)
    @Consumes(MediaType.TEXT_PLAIN)
    public Integer regStation(@PathParam("serial") String serial,
    @PathParam("name") String name,
    @PathParam("longitude") Double longitude,
    @PathParam("latitude") Double latitude);

    /* Unregistration of the station */
    @DELETE
    @Path("/register/id/{id}")
    public void unregStation(@PathParam("id") Integer id);

}

Name, longitude and latitude are set in application.properties and can be set by ENV variables from CRI.

Running the application in production mode

From Fedora IOT on Raspberry PI, execute the following commands

Env Variables

SENSORHOST: FQDN or IP of Python Sensor Service ex: (sensor)
TEAMNAME: Name of Edge Device or Team (default: fr_FR.UTF8)
TEAMLONGITUDE: Longitude of Device (default: -3.65)
TEAMLATITUDE: Latitude of Device (default: 47.09)

Production

podman run \
-v /etc/machine-id:/etc/machine-id:ro \
-e SENSORHOST=changeme \
-e TEAMNAME=QIoT.fr_FR.UTF8 \
quay.io/acb-fr/qiot-edge-service-jvm:1.1

Debug Mod

podman run \
-it --rm
-v /etc/machine-id:/etc/machine-id:ro \
quay.io/acb-fr/qiot-edge-service-jvm:1.1

Difficulties

int into Double

We have two IOT runtime environments. Two Raspberries, two probes, two geographic sites. One is used for production, the other for development. We built a fully functional Edge Service image on the Pre-Production environment but with this same image we encountered problems in the Production environment. We had the following error.

2020-09-28 08:40:14,597 ERROR [io.sma.rea.mes.provider] (main) SRMSG00200: The method fr.axians.qiot.edge_service.service.telemetry.TelemetryService#streamGasData has thrown an exception: java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Double (java.lang.Integer and java.lang.Double are in module java.base of loader 'bootstrap')

We had wasted time trying to figure out why we had an error in the code when the code version was exactly the same.

After investigation, the problem came from the QIoT Sensor Service which returned an INT for the ADC value when it could not find a value to harvest. While the QIoT Edge Service was waiting for a Double.

// adc in Double
{
  "result":{
    "adc":0.0,
    "instant":"2020-09-28 09:04:29GMT",
    "nh3":363047.61904761917,
    "oxidising":22772.37851662404,
    "reducing":26907.133243606997
  }
}
// adc in INT
{
  "result":{
    "adc":0,
    "instant":"2020-09-28 09:05:29GMT",
    "nh3":363047.61904761917,
    "oxidising":22772.37851662404,
    "reducing":26907.133243606997
  }
}

In particular, we lost time on this problem because we worked in a decentralized manner and the Edge Service sub-team had not seen the operation of the Sensor Service part.

-PNative

When we packaged our application and launched our executable application we encountered the error below. This problem seems to be associated with the RestEasy library that allow our code to read and interprete JSON into Java object. When we worked in our development environment we did not have any problem but due to unknown reason we had it in a Pnative mode. We have try to modify different type of our variable and modify our code to properly transform the variable we get from our RestClient Interface. But until we still did not have a solution. Of course our team has an idea of the problem and at matter of fact that is our python team that have build the image for us we think that those beared men have tried to sabotage us .. ;p

__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
[...]
2020-09-28 08:41:49,847 WARN  [org.jbo.res.res.i18n] (main) RESTEASY002165: No valueOf() method available for int, trying constructor...
2020-09-28 08:41:49,850 ERROR [io.sma.rea.mes.provider] (main) SRMSG00200: The method fr.axians.qiot.edge_service.service.telemetry.TelemetryService#streamGasData has thrown an exception: javax.ws.rs.ProcessingException: java.lang.IllegalArgumentException: RESTEASY003360: int has no String constructor

Contributors