How to build a fleet tracking and compliance reporting system with a Particle Boron

A Particle Boron MCU with an added GPS module could easily report, in-real time, the locations and mileage of any given vehicle.

Chris DeLaurentis article author avatarChris DeLaurentisMay 14, 2024

Problem statement

The IFTA reporting is a requirement for businesses with trucks across the USA. It is incumbent upon businesses to track how much fuel they use, the types of fuel they use and how far they drive in each jurisdiction. A simple GPS-enabled Particle device with Google Maps integration could easily solve this onerous reporting requirement in a cost-effective manner.



A Particle Boron MCU with an added GPS module could easily report, in-real time, the locations and mileage of any given vehicle. As an added bonus tapping into the ODB2/J1939 connector for engine diagnostics and possibly real-time fuel levels. As an added, added, super bonus, perhaps we could log the data out to a block chain or distributed ledger for immutability and transparency.

Solution will utilize the built in Google integrations of Particle as well as another 3rd party such as Datacake or Losant for dashboard & reporting visualizations.


The author as a child with his father in front of a tractor-trailer.

Growing up in a big construction family everyone always told me, “Kid don’t go into construction!“ 

I took that to heart and I ended up becoming a technologist instead. As fate would have it, technology invaded construction, just like every other industry. Today’s cars, trucks, and heavy equipment are all enabled with microcomputers and telemetry that reports on every aspect of a vehicle or machine. From run-time engine stats to the angle of an accelerator’s pedal, equipment is data-enabled and throws off valuable insights constantly. Combined with GPS location data, a company can now have a complete picture of the location and health of their equipment. 

That brings us to the International Fuel Tax Association or IFTA. The IFTA and subsequently, the IFTA fuel tax report requirement, is a requirement for businesses with trucks across the USA and Canada. It is incumbent upon businesses to track how much fuel they use, the types of fuel they use and how far they drive in each jurisdiction. This is where a simple GPS-enabled Particle device with Google Maps integration could easily solve this onerous reporting requirement in a cost-effective manner. In this post I’ll be documenting the journey to build a GPS-enabled, CAN bus integrated Particle device that reports not only location in real time but also fuel consumption as reported by the vehicle itself. So here we go…


Project plan

With all projects I start, I like to sketch my ideas out in paper & pencil. My sketches are usually pretty goofy and over simplified but it helps me get ideas out of my head. 

An initial project drawing

An initial project drawing

The major components needed here are pretty straightforward:

  • Compute: The MCU, of course. In this case, we’re using the Boron 404X with its seamless cellular connectivity. 
  • CAN Communication: A transceiver to talk to the vehicle’s CAN bus via the OBD2 port or, in the case of most heavy equipment and trucks, the J1939 port. In addition to the transceiver we need to be able to connect to the port properly and thus a proper connector(s) will be needed. 
  • Location Tracking: A GPS module for more accurate location tracking. While I can use the approximated GPS coordinates from the MCU’s cellular tower position, I want to be a little closer to mark in my tracking. 
  • Power: I can quite easily power the system via the MCU and a LiPo battery. Alternatively, I can power the MCU off the vehicle’s 12V power via the OBD/J1939 connector so I’ll need a buck converter to set the appropriate voltage for the MCU. 
  • Event Delivery: All this data can be simply reported in a simple “publish” call to Particle’s event publishing API. 
  • Data Visualization: A dashboard with GPS locations and engine stats 

With my rough idea in hand, I started collecting materials. 

Bill of materials


Skills & tools required 

For this project, you’ll need

  • A Particle account and a basic understanding of the particle pub/sub capability
  • Some light soldering if you want to perf board the components or just leave it on the breadboards
  • Particle-flavored, Arduino-style, C++ development experience


Here’s a quick Fritzing of the components on breadboards. The GPS module utilizes the native UART TX & RX pins of the Boron while the OLED is connected to the I2C SCL and SDA pins. The transceiver utilizes the SCK clock and MOSI & MISO for I/O via the SPI pins. I’ve picked the A1 pin for the interrupt pin and the A2 pin for the chip select (CS) pin. The Boron really makes it easy to communicate with many different protocols all at the same time. Plenty of other pins to spare!


A fritz of the components on a breadboard


A small note about CAN and the OnBoard Diagnostic. Over the years there have been a variety of competing and proprietary standards for communicating with the on board compute of vehicles. The auto and equipment industry finally sort of shook out and settled on the Controller Area Network as the de facto, extensible choice for talking to the various electronic control units (ECU). Built on top of this standard are a number of higher level protocols such as CANOpen and DeviceNet. We won’t go into those here but be aware that CAN is a pretty open and addressable bus standard that can do a lot of stuff. In this project we’re just going to focus on sniffing the CAN frames that come across the bus and directly posting frames to request specific measurements, namely Fuel Rate and Fuel Type. In this project we’ll be posting the standard OBD2 PIDs. However, as stated in that wiki article “not all vehicles will support all PIDs and there can be manufacturer-defined custom PIDs that are not defined in the OBD-II standard.” More on that later. 

To connect to the OBD of the vehicle we’re going to use the standard OBD2 connector so that we can plug into any standard commercial vehicle. We’ll then use a connector that takes the standard OBD2 male end and plugs into a J1939 cable so that we can plug it right into heavy equipment. This will give us the flexibility to address any type of vehicle fleet. 


The standard OBD2 connection has 16 pins. Of these we can see in the following diagram that the 2 CAN lines, high & low, can be found on pins 6 and 14, respectively. The signal ground for CAN is on pin 5 while chassis ground is on pin 4 and 12V battery power is on pin 16. 


Check out this diagram from CSS Electronics

A quick note on the OBD2 pigtail: The product images on amazon show a chart with the wire colors on the open and their respective pin assignments. This… is incorrect. 

I learned this the hard way after soldering the pigtail to some perfboard (please don’t judge my soldering skills). I had to do a continuity test for each pin with my multimeter to determine this. The correct pin to wire mapping is as follows, hopefully that’ll save you some headaches:


OBD2 Pigtail Pin Map

1 2 3 4 5 6 7 8
brown brown-white purple orange light blue green black black-white
9 10 11 12 13 14 15 16
red-white white yellow pink grey green-white dark blue red


J1939 Connector

A (ground) B (+V) C (CAN High) D (CAN Low)
5 16 6 14


So the pins we need to connect for the OBD are 6 (green), 5 (light blue), and 14 (green with a white stripe). You can hook those directly to the CAN transceiver’s respective high and low screw terminals and to the ground line. 



For powering the system we have a couple options. The simplest way is, obviously, to plug a LiPo battery into the JST connector. However, if you really wanted to, you could add a buck converter into the mix. Using a buck converter, you can tap the +12 volt pin (16) of the OBD connector and the chassis ground pin (4) for power. This way you can make the entire project run off the car’s power. 

Software apps and online services

  • VS Code w/ the Particle Workbench extension installed
  • Particle’s Webhook Integration (for calling to the Google Maps geocoder)
  • Google Maps API (for geocoding cellular tower locations)


Webhook integration & a slight tweak

As a fall back GPS solution for when the GPS module can’t get a fix on satellites, I’m relying on the approximate GPS location by using the cellular towers and some geocoding via the super handy Locator Library by the prolific Rick Kaseguma. I’m not going to reiterate what’s in the readme there for setting up Google API key or the webhook integration but I am going to point out a couple tweaks to the library there. Namely, the current version on Github references the 0.0.3 version of the CellularHelper. I ran into some issues with this as it’s much older than the current 0.2.7 version available here. So I’m using 0.2.7. In addition to that, while developing, I had some issues with how the locator library was resolving location when on an LTE connection. Specifically, I added some fallback logic for when the AT CGED commands fail so that it utilizes the “cellular_global_identity” function from the CellularHelper library to find the tower details. 

In addition to the webhook for GPS resolution, we’ll also be setting up 2 webhook integrations to send the data events off to Losant for visualization. More on that later. 


Particle logic function

After we publish data from our device to the Particle platform we’re going to want to scrub up and process the raw data a little before we visualize it. This is where the very handy platform Logic Function capability comes into play. For this project we’re going to use a logic function to reformat the raw CAN data that we’ll be publishing to something more nuanced. As mentioned earlier, vehicle manufacturers are not obliged to respond or adhere to standard PIDs within their vehicles. In fact, what I’ve found is that most manufacturers hold a set of their own, non-standard, private PIDs, as mentioned here, and they don’t share them without a hefty fee. Much work has been done in the open-source community to reverse engineer and share findings. Comma’s openPilot project has a DBC repository, here, where they are collecting various vehicle PID codes. 

And this is where the Particle Logic Function comes into play. By publishing the CAN data that we pull off the bus as a general event, in this project I’m posting is as “can_data_raw”, we can have Particle Logic Function pick up on that generalized event and massage it into a specific form and re-post as a specific event. Depending on the vehicles in the fleet, we can put some intelligence into the Logic Function so as to fine tune our CAN events before they’re sent off to display on our dashboard.  My Logic Function republishes the event as “can_data_update” to then be picked up by my Losant Webhook integration. This is a simple one liner in the Logic Function: 


  Particle.publish(“can_data_updated”, reformattedData, { productId: event.productId, asDeviceId:event.deviceId });


Note, I set the “asDeviceId” field member to the deviceId from the incoming event. This is important because, by default, the Logic Function sets the “coreId” field member of the outbound event being published to “logic”. This is important because our Losant workflow takes in the “coreId” field to identify the device in their platform. 


The code



Check out the final code for the project here


I implemented a class around the libraries for the GPS module, the CAN module, and the OLED modules. These are the GPSManager, CANManager, and DisplayManager, respectively. 


The main for this project is src/FleetTracker.cpp. In this file I setup:

  • The geocodedlocationCallback function to process the webhook callback responses
  • The GPSManager, CANManager, and DisplayManager classes
  • Main event loop logic
  • Various and sundry supporting utility functions


The main event loop does a couple things every go round. 

  • Call for updates
  • Publish updates on an interval
  • Update the OLED display


Now we’re ready to display the fruits of our labor. For visualization of the location and CAN data I’ve decided to wire up 2 web hooks to the Losant platform. One webhook sends off the GPS location data and the other webhook sends off the CAN event data. This couldn’t be easier using the Particle webhook integration and a webhook endpoint set upon the Losant side. Check out these instructions from Losant on how to easily setup a webhook and dashboard that can display the device location on a map as well as any device attribute you wish to add. Make sure you set up the webhooks under the section of your Particle dashboard for your “product” so that the data that is isolated to your products can be picked up by the webhook configuration. 

In this scenario I have 2 data events, GPS & CAN, that land on the same Losant endpoint. The Losant workflow first ingests and identifies the device from the incoming “coreId” field of the payload. Then it converts the payload to JSON and looks for location data in the payload. If it’s there it goes up to update the location field on the device in Losant. If not, it goes on to look for the CAN data fields that were added by my logic function. In this case I added a “rpm” field and a “fuel_rate” field to the payload that get pulled in to be displayed on the dashboard. 


Losant’s no-code workflow builder


Rough dashboard w/ CAN data for RPM, fuel rate & location

Particle’s Product Fleet Health dashboard showing event and integration traffic


So! That’s it! As you can see here I’ve pulled together some off the shelf components from Amazon and wired them up with the super handy Particle Boron MCU. With some firmware, a Logic Function in javascript, and some webhooks I am able to present real time GPS and vehicle engine data in a dashboard that can be exported for IFTA compliance amongst many other use cases. 


Final Assembly on perf board


Final Assembly with Photon 2 wired up for simulating CAN via OBD2


Comments are not currently available for this post.