22 November, 2023

Unexpected REST selectors

Use cases of breaking the limits with flespi API.

As we remember from Steve McConnell, "managing complexity is a primary technical imperative." Hierarchy is one of the most common concepts for managing complexity. The hierarchical principle is evident in the guidelines for constructing paths in the URI of REST API. Here, the structure of a URL is like:

https://{base-URL}/{optional-versioning}/{resource}/{resource-identifier}/{subresource}/{subresource-identifier}/etc

Various REST API guidelines advise the use of the following resource identifiers*:

  • numeric IDs or custom encoded IDs
  • UUIDs/GUIDs
  • names
  • hashes
  • etc.

*These guidelines suggest divergent concepts, noting that identifiers should be unique to each instance of the resource and should remain stable over the resource's lifetime.

At flespi, we use the term ‘selectors’ for resource identifiers, as they are used to select specific entities in a REST API call. The evolution of REST selectors has been quite remarkable: from simple comma-separated lists of IDs to selecting instances by mask in all fields, then expanding to neighboring instances, and finally to the ability to select devices by literally everything.

The most basic examples can be seen in the appropriate Knowledge Base reference. In this article, we'll consider some examples that may inspire you to rethink how you use the flespi API. Because we claim that flespi is a back-end for telematics platforms, and our API is the backbone of your applications.

Consider the following scenarios where the flespi API enhances efficiency:

  • if you can reconfigure 1,000 devices by invoking a single API call instead of 1,000, it will make your application faster;

  • if you can identify a set of devices with certain problems with one API call instead of manually analyzing the data or wasting time writing complex algorithms, it may lower your costs.


Use cases

Refuel cars nearby

Task: You run a car-sharing service. You need to inform your worker about which cars need to be refueled and are within a 2km radius.

Solution: The refueling condition can be articulated as "parameter 'fuel.level' is lower than 15" (alternatively, you may use parameters like 'remaining.range' or 'fuel.volume'). The 2km distance condition can be evaluated using the expression function 'distance(X1, Y1, X2, Y2)', where X1, Y1 represents the worker's position, and X2, Y2 is the position of the vehicle derived from telemetry. Both conditions must be satisfied, which requires a binary 'AND' operation.

Selector:

{telemetry.fuel.level<15 && distance(telemetry.position.latitude, telemetry.position.longitude, 54.72, 25.26) < 2}


Schedule maintenance

Task: You manage a fleet of vehicles. One of your fleets, equipped exclusively with Teltonika devices, reports overall mileage. These trucks must undergo maintenance every 15,000 km or when problems are detected from the OBD dongle. You need to identify trucks with OBD issues and those that have less than 1,000 km before the next maintenance milestone to schedule service.

Solution: The mileage value comes from 'can.vehicle.mileage'; to calculate the remaining kilometers to the next 15,000 km marker, use the expression '15000 - (can.vehicle.mileage - (15000 * floor(can.vehicle.mileage / 15000)))' - (sounds quite complicated but the idea is that "15000 * floor(can.vehicle.mileage / 15000)" - is the closest lower point dividable by 15000, we subtract it from actual can.vehicle.mileage to get how much the vehicle moved after the last maintenance, and subtract it from 15000 to know how much left). We may detect OBD issues by checking if parameters such as 'can.mil.status', 'can.j1939.dm1.dtc.mil', 'can.mil.time', 'can.dtc', or 'can.dtc.number' are present, or for simplicity, verify if 'can.dtc' exists in the telemetry using 'exists()' expression operator. As any one of the conditions should qualify the vehicle, a binary 'OR' operation is used.

Selector:

 {15000 - (can.vehicle.mileage - (15000 * floor(can.vehicle.mileage / 15000))) < 1000 || exists("telemetry.can.dtc")}


Reconfigure storage consumption devices

Task: Some devices are generating more reports than necessary, causing excessive storage use on flespi. You need to reconfigure Queclink GV300 model devices to report less frequently if they have a message storage period of over two months and the occupied storage exceeds 100MB, and only if they report to a channel with ID=12345.

Solution: The device model can be specified by 'device_type_name or device_type_id'. The message storage period and storage size are indicated by 'messages_ttl' and 'messages_size' fields, respectively. Channel ID is determined from the telemetry. All criteria must be met, thus a logical 'AND' operation is used for all conditions.

Selector:

{device_type_name="gv300" && messages_ttl > 2 * 30 * 86400 && messages_size > 100 * 1024 && telemetry.channel.id = 12345}


Ask your pal to bring you pizza on a way home

Task: You are a fleet dispatcher who fancies a pizza at the end of the day, but you don't want to pay for delivery. You aim to identify all vehicles close to the restaurant, with the engine on, heading in your direction, and driven by colleagues from your company.

Solution: First and foremost, do not abuse your official duties! :) However, if it's permissible, you can locate a suitable vehicle as follows:

  • the ‘last_active’ field indicates vehicles recently online (e.g., within the last 300 seconds); combine this with ‘engine.ignition.status’ from telemetry.

  • use the ‘distance()’ function (as shown in the first example) to determine the vehicles in vicinity to the pizza shop (make sure to input the correct coordinates).

  • the ‘position.direction’ parameter indicates the vehicle's direction; apply it with an 'AND' condition twice to define the directions range.

  • the ‘driver.name’ plugin field commonly assigns a driver to a vehicle. It may be used several times within an expression with the ‘OR’ logical condition to check the driver against your trusted list.

Expression:

{last_active < 300 && telemetry.engine.ignition.status == true && distance(telemetry.position.latitude, telemetry.position.longitude, 54.719446, 25.2971513) < 2 && telemetry.position.direction > 90 && telemetry.position.direction < 145 && (plugins.fields.driver.name == "Brad Pitt" || plugins.fields.driver.name == "Meryl Streep" || plugins.fields.driver.name == "Denzel Washington")}

Conclusion

Remember that with great power comes great responsibility. If you have an account with 50,000 devices, some complex expressions can take time (and definitely will) along with flespi computational resources to evaluate. Therefore, use this feature wisely.