Driving Time and Distance using Waze [4.0.0.0;4.9.9.9]

This rule template uses the Waze RoutingManager API to query for and calculate the time and distance to drive between two points. My original idea for this template was to get an alert in the mornings so we know when to leave early because there’s yet another accident on the interstate.

The template has four properties:

  • starting coordinates in “lat,lon” format
  • ending coordinates in “lat,lon” format
  • Number:Time Item that will be updated with the current driving time
  • Number:Length Item that will be updated with the current driving distance

This rule template does not have any triggers or conditions. To avoid hammering the Waze servers this rule remains inert until you further add your own triggers and conditions. I recommend a polling period in minutes at the most and adding conditions so the rule does not run at times when it’s not needed.

If more than one route is desired, create multiple rules using this template, one for each route.

Language: JS Scripting

Dependencies:

  • JS Scripting add-on installed
  • Future versions may depend on OHRT

Changelog

Version 0.2

  • added property to choose region to avoid Internal Error problems for users outside the US
  • changed the logger name and added more logging at the debug and trace levels
  • added better error checking and reporting

Version 0.1

  • initial release

Resources

uid: rules_tools:drive_time
label: Driving Time
description: When run it calculates the time and distance to drive between two fixed points.
configDescriptions:
  - description: Which Waze routing server to use. You will get an "Internal Error" if you choose the wrong one. Defaults to "US".
    label: Region
    name: region
    required: false
    defaultValue: US
    type: TEXT
    options:
      - label: 'US'
        value: 'US'
      - label: 'EU'
        value: 'EU'
      - label: 'IL'
        value: 'IL'
      - label: 'AU'
        value: 'AU'
    limitToOptions: true
  - description: Starting point latitude,longitude 
    label: Start
    name: startPoint
    required: true
    type: TEXT
    context: location
  - description: Ending point latitude,longitude 
    label: Destination
    name: destPoint
    required: true  
    type: TEXT
    context: location
  - name: timeItem
    label: Travel Time Item
    description: Item that stores the travel time.
    type: TEXT
    context: item
    filterCriteria:
      - name: type
        value: Number:Time
    required: true
  - name: distanceItem
    label: Travel Distance Item
    description: Item that stores the travel distance.
    type: TEXT
    context: item
    filterCriteria:
      - name: type
        value: Number:Length
    required: true
triggers: []
conditions: []
actions:
  - inputs: {}
    id: "1"
    configuration:
      type: application/javascript
      script: >
        // Version 0.2

        var loggerBase =
        'org.openhab.automation.rules_tools.DrivingTime.'+ruleUID;

        console.loggerName = loggerBase;

        //osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName,
        'DEBUG');


        // Rule Properties

        var FROM_COORD = "{{startPoint}}".split(',');

        var TO_COORD = "{{destPoint}}".split(',');

        var timeItem = items.{{timeItem}};

        var distanceItem = items.{{distanceItem}};

        var region = "{{region}}";


        // Locationms

        var FROM_LON = FROM_COORD[1].trim();

        var FROM_LAT = FROM_COORD[0].trim();

        var TO_LON = TO_COORD[1].trim();

        var TO_LAT = TO_COORD[0].trim();


        // URL

        var WAZE_URL = "https://www.waze.com/"

        var BASE_URL = "RoutingManager/routingRequest?"

        if(region == "EU" || region == "AU") BASE_URL = "row-"+BASE_URL;

        if(region == "IL") BASE_URL = "il-"+BASE_URL;

        var URL = WAZE_URL+BASE_URL
                  +"from=x%3A"+FROM_LON+"+y%3A"+FROM_LAT+"+bd%3Atrue"
                  +"&to=x%3A"+TO_LON+"+y%3A"+TO_LAT+"+bd%3Atrue"
                  +"&returnJSON=true&returnGeometries=true&returnInstructions=true&timeout=60000&nPaths=1"

        // Headers

        var headers = new Map();

        headers.set("User-Agent", "Mozilla/5.0");

        headers.set("referer", WAZE_URL);


        console.debug("Getting the distance and time to travel between " +
        FROM_COORD + " to " + TO_COORD + " using URL " + URL);



        var rawJSON = actions.HTTP.sendHttpGetRequest(URL, headers, 60000);

        console.trace("Results: \n" + rawJSON);


        var parsed = JSON.parse(rawJSON);


        if(parsed.response === undefined) {
          console.error('Failed to retrieve a parsable response from Waze. "Internal Error" may mean you selected the wrong region. Response:\n' + rawJSON);
        }

        else {
          // Calculate time and distance
          var time = parsed.response.results.map( r => Quantity(r.crossTime + ' s') ).reduce( (total, curr) => total.add(curr), Quantity('0 s') );
          var distance = parsed.response.results.map( d => Quantity( d['length'] + ' m' ) ).reduce( (total, curr) => total.add(curr), Quantity('0 m') );

          console.debug("Time is " + time.toUnit("min") + " and distance is " + distance.toUnit("mi"));
          timeItem.postUpdate(time);
          distanceItem.postUpdate(distance);
        }
    type: script.ScriptAction

5 Likes

Hello everyone,

unfortunately this does not work for me, no matter whether I try from the rule or directly from a browser, the feedback is always.

My request looks like this

https://www.waze.com/RoutingManager/routingRequest?from=x%3A6.655048727989198+y%3A51.41853106532269+bd%3Atrue&to=x%3A6.735112667083741+y%3A51.131318036426684+bd%3Atrue&returnJSON=true&returnGeometries=true&returnInstructions=true&timeout=60000&nPaths=1
{"error": "Internal Error"}

Unfortunately this API is not well documented. I mostly figured it out by trail and error.

But I did discover there are different routing servers for different regions of the world in a python script (WazeRouteCalculator/WazeRouteCalculator/WazeRouteCalculator.py at master · kovacsbalu/WazeRouteCalculator · GitHub).

        'US': 'RoutingManager/routingRequest',
        'EU': 'row-RoutingManager/routingRequest',
        'IL': 'il-RoutingManager/routingRequest',
        'AU': 'row-RoutingManager/routingRequest'

Obviously that list is not comprehensive across the world, and I’m not sure what region IL is (Israel? Waze was founded as an Israeli company before being purchased by Google).

If you are in the EU try replacing `“RoutingManager/routingRequest” with “row-RoutingManager/routingRequest” in the URL and let me know if that works.

I’ll figure out how to make that part of the URL configurable through a property.

Hi @rlkoshak, thanks for the rule and your answer.
I am from Germany so EU, and yes with

 'EU': 'row-RoutingManager/routingRequest',

the rule works.

Perhaps an error query should be included if the rawJSON return value does not contain

{“response”:{“results”:[{"path”

Already does. A new version of the rule template is posted now which has a property to choose the region, logs out the raw JSON at the trace level, and logs our what;'s returned by the server if the parsed JSON doesn’t include what we expect.

I just got done testing it and verifying it works so remove your existing rule and template, add back the template and create a new rule. You should now have a property to choose the region which will change the URL of the request.

For an example of alerting when the travel time gets too long or a different route from usual is see below. I configured the rule template to only run every minute between 07:00 and 07:20.

items.School_CheckTime.postUpdate(time.toZDT());

var usualDistance = Quantity("17.6 mi");
var distanceBuffer = Quantity("0.5 mi");
var usualTime = Quantity("19.1 min");
var timeBuffer = Quantity("5 min");
var notificationId = "schoolDriveTime";

console.debug('Received travel info from Waze');

var arrivalTime = time.toZDT(items.School_TravelTime).toLocalTime();

function getTime(inst) {
  let hour = inst.hour();
  let meridian = "AM"
  if(hour == 12) meridian = 'PM';
  if(hour > 12) {
    hour -= 12;
    meridian = 'PM'
  }
  
  return hour + ":" + String(inst.minute()).padStart(2, '0') + " " + meridian;
}

// If route is more than distanceBuffer from the usual route
var differentRoute = items.School_TravelDistance.quantityState.subtract(usualDistance).greaterThan(distanceBuffer);
// If the travel time is more than timeBuffer from the usual route
var longerTime = items.School_TravelTime.quantityState.subtract(usualTime).greaterThan(timeBuffer);

var msg = "As of " + getTime(time.toZDT().toLocalTime());
if(differentRoute) msg += " a different route is suggested by Waze"
else msg += " the usual route is still the best"
msg += " to school and travel duration is " 
       + items.School_TravelTime.quantityState.toUnit("min").float.toPrecision(3)
       + " minutes for an arrival time of " + getTime(arrivalTime);

console.info(msg);
items.School_TravelMessage.postUpdate(msg);

if(differentRoute || longerTime) {
  actions.notificationBuilder(msg).addUserId('rlkoshak@gmail.com')
                                  .withTitle('Time to leave!')
                                  .withIcon('f7:car-fill')
                                  .withOnClickAction('app:android=com.waze') // untested
                                  .withReferenceId(notificationId)
                                  .send();
  cache.private.get(notificationId)?.cancel();
  cache.private.put(notificationId, actions.ScriptExecution.createTimer(ruleID, time.toZDT('PT10M'), () => {
    actions.notificationBuilder('cancel notification').withReferenceId(notificationId).hide().send();
    cache.private.remove(notificationId);
  }));
  actions.Voice.say(msg, null, "sink:id"); // all speakers group
}
else {
  cache.private.get(notificationId)?.cancel();
  actions.notificationBuilder('cancel notification').withReferenceId(notificationId).hide().send();
  cache.private.remove(notificationId);
}

The School_TravelMessage Item is a String Item I use with Embedded Waze Live Traffic Map Widget as the footer so I can see the travel time and the current traffic conditions in MainUI in the same widget. I use =items.Shcool_TravelMessage.state for the footer property of the widget.