Thingy:91 Smart Car Tracker And Notification Service

About the project

I wanted to commute to work by bike but unfortunately I live some 75km away from my office. Mass transit isn't regular or convenient where I live (it's not set up for commuters) so car is the only viable alternative. I want to make sure my car is safe where I park it, so lets make it smart!

Project info

Items used in this project

Hardware components

Raspberry Pi 4 Model B 1GB, 2GB, 4GB or 8GB RAM Raspberry Pi 4 Model B 1GB, 2GB, 4GB or 8GB RAM This can be any raspberry pi and is optional, its just a server for Node-Red so almost any computer will do here. x 1
Sensor Tool W nRF52840 & nRF9160 Sensor Tool W nRF52840 & nRF9160 x 1

Software apps and online services

Telegram Telegram For sending updates to a smart phone
nRF Cloud nRF Cloud comes with the Nordic Thingy:91
Node-Red Node-Red For retrieving data from the nRF Cloud API and sending messages to a Telegram bot

Story

The idea of the project was to keep any eye on my car while I was off at work, so I knew that it would be there when I got back to it.  Crime isn't too bad where I leave the car, but I'd rather make sure it was in the same state that I left it in.  I wanted to put a GPS tracker on the car with an alert system if it moved, and if possible set the accelerometer to alert me if there was an impact to the car.  I also wanted this project to be extremely easy to replicate and as close to a standard build so I didn't have to maintain it going forward.  

I used one of the default builds that came with the Thingy:91, thingy91_asset_tracker_v2_nbiot, but before taking a dip into this I would suggest that you check your coverage for NBIOT and the Arkessa/iBasis sims or get a local provider that'll do NB-IoT.  There are other builds available that use LTE-m too if your network uses that instead.

After writing some basic python scripts to pick up the information from the API, I thought it might be easier not to re-invent the wheel and try using something a little more manageable long term and I haven't used Node-Red as yet so, now is as good a time as any.  

A. Setup Node Red

1. Install Node-Red

There are plenty of tutorials on how to set up Node-Red on many different devices.  I chose a Raspberry-Pi as I had one being used as a server at home anyway.  

2. Notifications

I wanted a way to send notifications, Telegram is free and relatively easy to set up and, again, there are plenty of tutorials on how to start a bot here and how to install it into node red so I won't go through that (other than to say click the 3 lines in the top right of the UI, select Manage Palette, select Install tab and search for Telegram - simple).


B. Thingy:91 Setup

1.  I'm presuming you've set up your thingy:91.  If not there are a few videos and tutorials on this as well 

https://www.nordicsemi.com/Products/Development-hardware/Nordic-Thingy-91/GetStarted

2. Flash the  thingy91_asset_tracker_v2 pre built firmware.

3. Once you've set that up there are some details here you'll need from the nRF Cloud site https://nrfcloud.com/. You'll need the Device Name (under Devices) and the API key (available under User Account by clicking the 3 lines in the top right of the webpage).



C. Node Red Flow Setup

Set up the flow in Node Red as follows:

1. Inject Node - You'll want to set an interval to pull the information from the nRF Cloud API as you can see from the below its set to every 10 minutes

2. Change Node - I've added a change node here to set part of the payload for the API call from the following node.  Make sure the string is in the format below (make sure to change your DeviceID to that of your Thingy:91


Value: {"deviceId":"[stick in your device ID here]","appId":"GPS","pageLimit":"1"}



3.  HTTP Request node - add with the following (your API key is required under the Token field below)

4. Function node - this contains the javascript to convert the response from the API to GPS coordinates we can use to determine if the coordinates differ and then send in a hyperlink 

Add the following to the On Message tab


  1. var data = msg.payload.items[0].message.data;
  2. var datasplit = data.split(",");
  3. var tempLat = datasplit[2];
  4. var NorS = datasplit[3];
  5. var tempLong = datasplit[4];
  6. var EorW = datasplit[5];
  7. var indexOfLongDecimal = tempLong.indexOf(".");
  8. var longMins = parseFloat(tempLong.substring(indexOfLongDecimal-2));
  9. longMins = longMins/60;
  10. var longitude = parseInt(tempLong.substring(0,indexOfLongDecimal-2)) + longMins;
  11.  
  12. var indexOfLatDecimal = tempLat.indexOf(".");
  13. var latMins = parseFloat(tempLat.substring(indexOfLatDecimal-2));
  14. latMins = latMins/60;
  15.  
  16. var latitude = parseInt(tempLat.substring(0,indexOfLatDecimal-2)) + latMins;
  17. longitude = parseFloat(longitude).toFixed(6);
  18. latitude = parseFloat(latitude).toFixed(6);
  19.  
  20. if (NorS == "S") latitude = -latitude;
  21. if (EorW == "W") longitude = -longitude;
  22.  
  23.  
  24. let coordObj = { "latitude": latitude, "longitude":longitude };
  25. return coordObj;

GPS coordinates tend to jump around a bit without the device actually moving so I'd suggest rounding to the returned coordinates to smaller numbers as the difference between the farthest point to the actual location of the Thingy is some 100 metres, so it can wander from point to point over 200 metres, giving a false alert!


As a result you may need to lose some accuracy in the alerts, so change the two ".toFixed(6);"(lines 17 and 18) above to a smaller decimal place e.g. ".toFixed(3);" should do however, I ended up using 2 decimal places (1.1km as it wandered a over the 111 metres accuracy that 3 decimal places gives). As you'll see later I hyperlink the cloud api as well just to be sure!

5. Function Node

I've popped in another function here to check if the coordinates have changed passing on a boolean value and structuring a telegram message to pass on.


Insert the following (also don't forget to include your Device ID in the botMsg line 28 and Telegram Chat ID on line 29):


  1. var coordinatesChanged = false;
  2. msg.payload = {};
  3. var lastCoordinates = context.get("lastCoordinates");
  4. if (lastCoordinates == null){
  5. context.set("lastCoordinates", msg);
  6. }
  7. else{
  8. if (msg.latitude != lastCoordinates.latitude)
  9. {
  10. coordinatesChanged = true;
  11. }
  12. if (msg.longitude != lastCoordinates.longitude)
  13. {
  14. coordinatesChanged = true;
  15. }
  16. if (coordinatesChanged == true)
  17. {
  18. context.set("lastCoordinates", msg);
  19. }
  20. }
  21. msg.payload["coordinatesChanged"] = coordinatesChanged;
  22. var botMsg = "Your car has moved to https://www.google.ie/maps/place/";
  23. botMsg += msg.latitude;
  24. botMsg += ",";
  25. botMsg += msg.longitude;
  26. botMsg += " n For more information see https://nrfcloud.com/#/devices/[deviceID]"
  27. msg.payload.chatId = '[TelegramChatID]';
  28. msg.payload.type = 'message';
  29. msg.payload.content = botMsg;
  30. return msg;

6. Switch Node

This is based on whether or not the location has moved - the payload.coordinatesChanged

7. Telegram Sender Node

This is just a standard node with my bot selected

8. Debug nodes

Last nodes after the switch are both standard debug nodes with one small change to the output so that it displays the complete msg object as I've added some custom properties into the flow


That's pretty much it.  Just start your initial timestamp process and check the debug to make sure it works.


Future Improvements

In my initial proposal I was hoping to have the accelerometer notify me if there was any impacts to the car (which has happened before) but unfortunately the accelerometer isn't exposed in the example code for the v2 asset tracker and sending the data to the cloud was above my skill level.  If it's added to an example in a future release (or somebody can help me with that part) I'll update the script and provide a prebuilt image to load onto the Thingy:91.  

The HTTP Request can be modified to get information from other sensors (see below) just change the value of the 'appId' on the Change node (C2 above) from GPS to any of those from below

{"AIR_PRESS", "TEMP", "AIR_QUAL", "LIGHT", "HUMID", "FLIP", "RSRP", "GPS", "AGPS", "CELL_POS", "MCELL", "SCELL", "WIFI", "BUTTON"}

I've also put some python functions together (below) as an example.  Just make sure you populate it with your API key (there may be some errors in there though but most of it works!)


  1. import requests
  2. import time
  3. import json
  4. import requests
  5. from datetime import datetime,timedelta
  6.  
  7. API_URL = "https://api.nrfcloud.com/v1/"
  8. api_key = ""
  9. device_name = ""
  10. from_time = datetime.now() - timedelta(days=1)
  11. app_ids = {"AIR_PRESS", "TEMP", "AIR_QUAL", "LIGHT", "HUMID", "FLIP", "RSRP", "GPS", "AGPS", "CELL_POS", "MCELL", "SCELL", "WIFI", "BUTTON"}
  12.  
  13.  
  14. def updateWaitTime(api_key, device_id):
  15. hdr = {'Authorization': 'Bearer ' + api_key}
  16. req = API_URL + "devices/" + device_id + "/state"
  17. return requests.patch(req, data=payload, headers=hdr)
  18.  
  19.  
  20. def fetch_device(api_key, device_id):
  21. hdr = {'Authorization': 'Bearer ' + api_key}
  22. req = API_URL + "devices/" + device_id
  23. return requests.get(req, headers=hdr, params=updateConfigJson)
  24.  
  25. def fetch_devices(api_key):
  26. hdr = {'Authorization': 'Bearer ' + api_key}
  27. req = API_URL + "devices"
  28. return requests.get(req, headers=hdr)
  29.  
  30. def fetch_messages_by_start_time(api_key, device_id, start_time):
  31. global from_time
  32. start_time_iso = start_time.isoformat() + "Z"
  33. query = {'deviceId':device_id, 'inclusiveStart':start_time_iso, 'pageSort':'desc'}
  34. hdr = {'Authorization': 'Bearer ' + api_key}
  35. req = API_URL + "messages"
  36. from_time = datetime.now()
  37. return requests.get(req, headers=hdr, params=query)
  38. def fetch_last_message_by_type(api_key, device_id, app_id):
  39. query = {'deviceId':device_id, 'appId':app_id, 'pageLimit':'1'}
  40. hdr = {'Authorization': 'Bearer ' + api_key}
  41. req = API_URL + "messages"
  42. return requests.get(req, headers=hdr, params=query)
  43. def fetch_message_by_type(api_key, device_id, app_id):
  44. query = {'deviceId':device_id, 'appId':app_id}
  45. hdr = {'Authorization': 'Bearer ' + api_key}
  46. req = API_URL + "messages"
  47. return requests.get(req, headers=hdr, params=query)
  48.  
  49. def fetch_last_location(api_key, device_id):#cell tower and assisted GPS - need to extract the actual location
  50. query = {'deviceId':device_id, 'pageLimit':'1', 'pageSort':'desc'}
  51. hdr = {'Authorization': 'Bearer ' + api_key}
  52. req = API_URL + "location/history"
  53. return requests.get(req, headers=hdr, params=query)
  54.  
  55. def fetch_location(api_key):
  56. query = {"latest":"true"}
  57. hdr = {'Authorization': 'Bearer ' + api_key}
  58. req = API_URL + "location/history"
  59. return requests.get(req, headers=hdr, params=query)
  60.  
  61. def get_lat_long():
  62. responseJson = fetch_last_message_by_type(api_key, device_id, "GPS").json()
  63. data = responseJson['items'][0]['message']['data']
  64. print("Coordinates: ", data)
  65. dataSplit = data.split(",")
  66. tempLat = dataSplit[2]
  67. NorS = dataSplit[3]
  68. tempLong = dataSplit[4]
  69. EorW = dataSplit[5]
  70. indexOfLongDecimal = tempLong.index(".")
  71. longMins = float(tempLong[(indexOfLongDecimal-2):])
  72. longMins = longMins/60
  73. longitude = int(tempLong[0:(indexOfLongDecimal-2)]) + longMins
  74. indexOfLatDecimal = tempLat.index(".")
  75. latMins = float(tempLat[(indexOfLatDecimal-2):])
  76. latMins = latMins/60
  77. latitude = int(tempLat[0:(indexOfLatDecimal-2)]) + latMins
  78. if NorS == "S":
  79. latitude = -latitude
  80. if EorW == "W":
  81. longitude = -longitude
  82. return latitude, longitude
  83.  
  84. def fetch_account_info(api_key):
  85. hdr = {'Authorization': 'Bearer ' + api_key}
  86. req = API_URL + "account"
  87. return requests.get(req, headers=hdr)

  88. response = fetch_devices(api_key)
  89. devicesJson = response.json()
  90. device_id = devicesJson['items'][0]['id']
  91. response = fetch_device(api_key, device_id)
  92. deviceJson = response.json()


Credits

Photo of Collie147

Collie147

Developer for the last 10 years but have worked in IT for the past 20. Diploma in sound engineering and a big interest in music and developing hardware / software solutions.

   

Leave your feedback...