ABRP: abetterrouteplanner.com

Send live data to abetterrouteplanner.com

Overview

abetterrouteplanner is probably the best website to plan a route with an EV and optimize the number of stops needed and where to charge your vehicle.

Moreover, it has a wonderful functionality, to update your plan, taking into account your live data, to adjust with the real consumption of the vehicle, by connecting an obd device and soft like EVNotify or Torque Pro.

This example should help you to send your live data from OVMS box.
Author:David S Arnold

Installation

You can use the embedded website tools/shell and tools/editor of the OVMS box to create the directory and file. This editor will allow you to paste the plugin code from the documentation, and also to configure your own token and car model (see Remark).

Files to be created in /store/scripts/
  • ovmsmain.js, if not already exists
  • sendlivedata2abrp.js, by copying the file below and setting your own ‘user token’

Remark to get your own ‘user token’, you must connect with your login to abetterrouteplanner in classic view. In Settings/more Settings, there’s an item ‘Live Car Connection’ with 2 buttons: ‘Setup’ and ‘View live data’.

  • Click on Setup
  • Click on Torque
  • Click 4 times on ‘Next’, until having two fields to copy: ‘Webserver URL’ and ‘User Email Address’.
  • Copy the field ‘User Email Address’ and paste it in the source code const MY_TOKEN = “>>> PASTE HERE <<<”;

New in version 1.2: Your own parameters (car model, abrp token but also api-url) are now stored as config parameters, you can consult them using the shell with config list usr or modify with config set command.

Finally reboot your OVMS module. This was a one-time configuration. You’re now ready. Test it with the shell page in the embedded web server using the command script eval abrp.info() and then with the command script eval abrp.onetime(). You can also do it with the mobile app.

Script code

This is a javascript code, to extract in the file sendlivedata2abrp.js.

Warning

note the minimum firmware version: 3.2.008-147

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/**
 *       /store/scripts/sendlivedata2abrp.js
 *
 * Module plugin:
 *  Send live data to a better route planner
 *  This version uses the embedded GSM of OVMS, so there's an impact on data consumption
 *  /!\ requires OVMS firmware version 3.2.008-147 minimum (for HTTP call)
 *
 * Version 1.3   2020   inf0mike (forum https://www.openvehicles.com)
 *
 * Enable:
 *  - install at above path
 *  - add to /store/scripts/ovmsmain.js:
 *                 abrp = require("sendlivedata2arbp");
 *  - script reload
 *
 * Usage:
 *  - script eval abrp.info()         => to display vehicle data to be sent to abrp
 *  - script eval abrp.onetime()      => to launch one time the request to abrp server
 *  - script eval abrp.send(1)        => toggle send data to abrp
 *  -                      (0)        => stop sending data
 *  - script eval abrp.resetConfig()  => reset configuration to defaults
 *
 * Version 1.3 updates:
 *  - Fix for rounding of fractional SOC causing abrp to report SOC off by 1
 *  - Fix for altitude never being sent
 *  - New convenience method to reset config to defaults
 * 
 * Version 1.2 updates:
 *  - based now on OVMS configuration to store user token, car model and url
 *  - review messages sent during charge
 *  - send a message when vehicle is on before moving to update abrp
 *
 * Version 1.1 fix and update:
 *  - fixed the utc refreshing issue
 *  - send notifications
 *  - send live data only if necessary
 **/



/*
 * Declarations:
 *   CAR_MODEL: find your car model here: https://api.iternio.com/1/tlm/get_carmodels_list?api_key=32b2162f-9599-4647-8139-66e9f9528370
 *   OVMS_API_KEY : API_KEY to access to ABRP API, given by the developer
 *   MY_TOKEN : Your token (corresponding to your abrp profile)
 *   TIMER_INTERVAL : to subscribe to a ticker event
 *   URL : url to send telemetry to abrp following: https://iternio.com/index.php/iternio-telemetry-api/
 *   CR : Carriage Return for console prints
 *
 *   objTLM : JSON object containing data read
 *   objTimer : timer object
 */

  const CAR_MODEL = "@@:@@:@@:@@:@@";
  const OVMS_API_KEY = "32b2162f-9599-4647-8139-66e9f9528370";
  const MY_TOKEN = "@@@@@@@@-@@@@-@@@@-@@@@-@@@@@@@@@@@@";
  const TIMER_INTERVAL = "ticker.60";                         // every minute
  const EVENT_MOTORS_ON = "vehicle.on";
  const URL = "http://api.iternio.com/1/tlm/send";
  
  const DEFAULT_CFG = {
    "url": URL,
    "user_token": MY_TOKEN,      
    "car_model": CAR_MODEL 
  };

  const CR = '\n';

  var objTLM;
  var objTimer, objEvent;
  var sHasChanged = "";
  var bMotorsOn = false;

  // initialise from default
  var abrp_cfg = JSON.parse(JSON.stringify(DEFAULT_CFG));

  // check if json object is empty
  function isJsonEmpty(obj) {
    for(var key in obj) {
      if(obj.hasOwnProperty(key))
        return false;
      }
    return true;
  }

  // Read & process config:
  function readConfig() {
    // check if config exist
    var read_cfg = OvmsConfig.GetValues("usr", "abrp.");
    print(JSON.stringify(read_cfg) + CR);
    if (isJsonEmpty(read_cfg) == true) {
      // no config yet, set the default values
      OvmsConfig.SetValues("usr","abrp.",abrp_cfg);
    } else {
      // config existing
      abrp_cfg.url = read_cfg.url;
      abrp_cfg.user_token = read_cfg.user_token;
      abrp_cfg.car_model = read_cfg.car_model;
    }
  }

  // Make json telemetry object
  function InitTelemetryObj() {
    return {
      "utc": 0,
      "soc": 0,
      "soh": 0,
      "speed": 0,
      "car_model": abrp_cfg.car_model,
      "lat": 0,
      "lon": 0,
      "alt": 0,
      "ext_temp": 0,
      "is_charging": 0,
      "batt_temp": 0,
      "voltage": 0,
      "current": 0,
      "power": 0
    };
  }

  // Fill json telemetry object
  function UpdateTelemetryObj(myJSON) {
    if(!myJSON){
      // if the data object is undefined or null then return early
      return false;
    }
    var read_num = 0;
    var read_str = "";
    var read_bool = false;

    sHasChanged = "";

    if (bMotorsOn) {
      sHasChanged = "_MOTORS-ON";
      bMotorsOn = false;
    }

    // using Math.floor avoids rounding up of .5 values to next whole number
    // as some vehicles report fractional values.  Abrp seems to only support integer values
    read_num = Math.floor(Number(OvmsMetrics.Value("v.b.soc"))); 
    if (myJSON.soc != read_num) {        
      myJSON.soc = read_num;
      sHasChanged += "_SOC:" + myJSON.soc + "%";
    }

    read_num = Number(OvmsMetrics.Value("v.b.soh"));
    if (myJSON.soh != read_num) {
      myJSON.soh = read_num;
      sHasChanged += "_SOH:" + myJSON.soh + "%";
    }

    if ( (myJSON.soh + myJSON.soc) == 0 ) {
      // Sometimes the canbus is not readable, and abrp doesn't like 0 values
      print("canbus not readable: reset module and then put motors on" + CR);
      return false;
    }

    //myJSON.lat = OvmsMetrics.AsFloat("v.p.latitude").toFixed(3);
    //above code line works, except when value is undefined, after reboot

    read_num = OvmsMetrics.AsFloat("v.p.latitude");
    read_num = read_num.toFixed(3);
    if (myJSON.lat != read_num) {
      myJSON.lat = read_num;
      sHasChanged += "_LAT:" + myJSON.lat + "°";
    }

    read_num = Number(OvmsMetrics.AsFloat("v.p.longitude"));
    read_num = read_num.toFixed(3);
    if (myJSON.lon != read_num) {
      myJSON.lon = read_num;
      sHasChanged += "_LON:" + myJSON.lon + "°";
    }

    read_num = Number(OvmsMetrics.AsFloat("v.p.altitude"));
    read_num = read_num.toFixed(1);
    if (read_num <= (myJSON.alt - 2) || read_num >= (myJSON.alt + 2)) {
      myJSON.alt = read_num;
      sHasChanged += "_ALT:" + myJSON.alt + "m";
    }

    read_num = Number(OvmsMetrics.Value("v.b.power"));
    myJSON.power = read_num.toFixed(1);

    myJSON.speed=Number(OvmsMetrics.Value("v.p.speed"));
    myJSON.batt_temp=Number(OvmsMetrics.Value("v.b.temp"));
    myJSON.ext_temp=Number(OvmsMetrics.Value("v.e.temp"));
    myJSON.voltage=Number(OvmsMetrics.Value("v.b.voltage"));
    myJSON.current=Number(OvmsMetrics.Value("v.b.current"));

    myJSON.utc = Math.trunc(Date.now()/1000);
    //myJSON.utc = OvmsMetrics.Value("m.time.utc");

    // read_bool = Boolean(OvmsMetrics.Value("v.c.charging"));
    // v.c.charging is also on when regen => not wanted here
    read_str = OvmsMetrics.Value("v.c.state");
    if ( (read_str == "charging") || (read_str == "topoff") ) {
      myJSON.is_charging = 1;
      read_str = OvmsMetrics.Value("v.c.mode");
      if (sHasChanged != "") {
        sHasChanged += "_CHRG:" + read_str + "(" + OvmsMetrics.Value("v.c.charging") + ")";
        print("Charging in mode " + read_str + CR);
      }
    } else {
      myJSON.is_charging = 0;
    }

    myJSON.car_model = abrp_cfg.car_model;

    return (sHasChanged !== "");
  }

  // Show available vehicle data
  function DisplayLiveData(myJSON) {
    var newcontent = "";
    newcontent += "altitude = " + myJSON.alt       + "m"  + CR;    //GPS altitude
    newcontent += "latitude = " + myJSON.lat       + "°"  + CR;    //GPS latitude
    newcontent += "longitude= " + myJSON.lon       + "°"  + CR;    //GPS longitude
    newcontent += "ext temp = " + myJSON.ext_temp  + "°C" + CR;    //Ambient temperature
    newcontent += "charge   = " + myJSON.soc       + "%"  + CR;    //State of charge
    newcontent += "health   = " + myJSON.soh       + "%"  + CR;    //State of health
    newcontent += "bat temp = " + myJSON.batt_temp + "°C" + CR;    //Main battery momentary temperature
    newcontent += "voltage  = " + myJSON.voltage   + "V"  + CR;    //Main battery momentary voltage
    newcontent += "current  = " + myJSON.current   + "A"  + CR;    //Main battery momentary current
    newcontent += "power    = " + myJSON.power     + "kW" + CR;    //Main battery momentary power
    newcontent += "charging = " + myJSON.is_charging + CR;         //yes = currently charging
    print(newcontent);
  }

  function InitTelemetry() {
    objTLM = InitTelemetryObj();
    sHasChanged = "";
  }

  function UpdateTelemetry() {
    var bChanged = UpdateTelemetryObj(objTLM);
    if (bChanged) { DisplayLiveData(objTLM); }
    return bChanged;
  }

  function CloseTelemetry() {
    objTLM = null;
    sHasChanged = "";
  }

  // http request callback if successful
  function OnRequestDone(resp) {
    print("response="+JSON.stringify(resp)+CR);
    //OvmsNotify.Raise("info", "usr.abrp.status", "ABRP::" + sHasChanged);
  }

  // http request callback if failed
  function OnRequestFail(error) {
    print("error="+JSON.stringify(error)+CR);
    OvmsNotify.Raise("info", "usr.abrp.status", "ABRP::" + JSON.stringify(error));
  }

  // Return full url with JSON telemetry object
  function GetUrlABRP() {
    var urljson = abrp_cfg.url;
    urljson += "?";
    urljson += "api_key=" + OVMS_API_KEY;
    urljson += "&";
    urljson += "token=" + abrp_cfg.user_token;
    urljson += "&";
    urljson += "tlm=" + encodeURIComponent(JSON.stringify(objTLM));
    print(urljson + CR);
    return urljson;
  }

  // Return config object for HTTP request
  function GetURLcfg() {
    var cfg = {
      url: GetUrlABRP(),
      done: function(resp) {OnRequestDone(resp)},
      fail: function(err)  {OnRequestFail(err)}
    };
    return cfg;
  }

  function SendLiveData() {
    if (UpdateTelemetry()) {
      HTTP.Request( GetURLcfg() );
    }
  }

  function Reactivate_MotorsOn() {
    bMotorsOn = true;
    SendLiveData();
  }

  function InitTimer() {
    objTimer = PubSub.subscribe(TIMER_INTERVAL, SendLiveData);
    objEvent = PubSub.subscribe(EVENT_MOTORS_ON, SendLiveData);
  }

  function CloseTimer() {
    PubSub.unsubscribe(objEvent);
    PubSub.unsubscribe(objTimer);
    objEvent = null;
    objTimer = null;
  }

  // API method abrp.onetime():
  //   Read and send data, but only once, no timer launched
  exports.onetime = function() {
    readConfig();
    InitTelemetry();
    SendLiveData();
    CloseTelemetry();
  }

  // API method abrp.info():
  //   Do not send any data, just read vehicle data and writes in the console
  exports.info = function() {
    readConfig();
    InitTelemetry();
    UpdateTelemetry();
    CloseTelemetry();
  }

  // API method abrp.resetConfig()
  //   Resets stored config to default
  exports.resetConfig = function() {
    OvmsConfig.SetValues("usr","abrp.", DEFAULT_CFG);
    print(JSON.stringify(abrp_cfg));
    OvmsNotify.Raise("info", "usr.abrp.status", "ABRP::config changed");
  }

  // API method abrp.send():
  //   Checks every minut if important data has changed, and send it
  exports.send = function(onoff) {
    if (onoff) {
      readConfig();
      if (objTimer != null) {
        print("Already running !" + CR);
        return;
      }
      print("Start sending data..." + CR);
      InitTelemetry();
      SendLiveData();
      InitTimer();
      OvmsNotify.Raise("info", "usr.abrp.status", "ABRP::started");
    } else {
      if (objTimer == null) {
        print("Already stopped !" + CR);
        return;
      }
      print("Stop sending data" + CR);
      CloseTimer();
      CloseTelemetry();
      OvmsNotify.Raise("info", "usr.abrp.status", "ABRP::stopped");
    }
  }

Usage

How to make it run:
  • install as described in ‘Installation’
  • add to /store/scripts/ovmsmain.js:
    abrp = require("sendlivedata2abrp");
  • script reload
With command lines in the OVMS android or iOS app, in the messages part:
  • script eval abrp.info() => to display vehicle data to be sent to abrp
  • script eval abrp.onetime() => to launch one time the request to abrp server
  • script eval abrp.send(1) => toggle send data to abrp
  • script eval abrp.send(0) => stop sending data
  • script eval abrp.resetConfig() => reset configuration to defaults
Also in the messages part, configuration can be updated with:
  • `` config set usr abrp.car_model <value>``
  • `` config set usr abrp.url <value>``
  • `` config set usr abrp.user_token <value>``