Vue Native Geolocation (Part two)

Phone showing map of a location in London, UK
Photo by Henry Perks on Unsplash

We’ve got a basic starting app set up in Part One and so now in Part Two we’ll create user location detection using the Vue Native geolocation API for mobile devices.

Privacy laws, either platform or legal requirements, demand that a user provide permission to allow an app obtain the user’s location. The geolocation API has built-in methods to handle the authorization flow.

The current Device API example in the Vue Native docs does not function as expected so we need to modify the code. The result will be a text display of the user location.

Template

The output needs to be wrapped in a template and a container gives us somewhere to put stuff. Views are used for content display and <view>’s may be embedded in other views.

Here we have a button implemented with the <touchable-opacity> Vue Native basic button component. “on-press” calls the “getLocation” method. The “{{ location }}” is the placeholder for the location data. For now, we’ll output all the data returned in “coords” from the “getLocation” method.

The “errorMessage” placeholder is fairly obvious.

<template>
  <view class="container">
    <touchable-opacity :on-press="getLocation">
      <text class="text-field-title">Get Location</text>
    </touchable-opacity>
    <text class="text-field-title">Location:</text>
    <text>{{ location }}</text>
    <text class="text-error">{{ errorMessage }}</text>
  </view>
</template>

Script

To start we need a couple of modules from Expo: Location and Permissions.

The “getLocation” method is the core logic.

For now, the data function returns two values: location which will have all the data from detecting the user location which will include lat, lng, altitude plus more. Then there is a placeholder for error messages.

There are a number of permissions required from a user such as camera or accelerometer access, and, of course, location which may be derived from device gps or other methods such as wifi location.

So, the Permissions API requires a specific argument that the user agrees to share. The API will display a dialog to the user allowing “always”, “when using app” and “deny”. Fairly self evident but there is a condition that we will address later where the user has globally denied access in the device settings.

Upon “allow” the code executes “getCurrentPositionAsync” and this obviously gets the user location. The actual data returned is an object named “coords” which is accessed by dot notation and then assigned to “location” that is displayed on the device screen.

<script>
import * as Location from "expo-location";
import * as Permissions from "expo-permissions";

export default {
  data: function() {
    return {
      location: "",
      errorMessage: ""
    };
  },
  methods: {
    getLocation: function() {
      Permissions.askAsync(Permissions.LOCATION)
        .then(status => {
          if (!status.granted) {
            this.errorMessage = "Permission to access location was denied";
          } else if (status.granted) {
            Location.getCurrentPositionAsync({}).then(location1 => {
              this.location = location1.coords;
              this.errorMessage = "";
            });
          }
        })
        .catch(err => {
          console.log(err);
        });
    }
  }
};
</script>

Style

Pretty standard styling but one thing to note when referring to Expo or React Native documentation is that Vue Native requires Hyphen-case or PasCalCase for style properties and names as well as in the script section for component names and other definitions.

<style>
.container {
  background-color: white;
  align-items: center;
  justify-content: center;
  flex: 1;
}
.text-field-title {
  color: blue;
  font-size: 18;
  margin: 10;
}
.text-error {
  color: red;
}
</style>

Result

Tadaa! Output to the screen is lat/lng in decimal degrees, altitude in meters, some accuracy data, and speed and direction if moving. Each of these parameters may be individually accessed by dot notation such as:

this.lng = location.coords.longitude

Note the ‘this’ which has to refer to a data parameter of “lng”. “{{ lng }}” can then be used to output to the device screen.

Next

In the next Tech Note, we’ll look at passing the location data as a prop to a map component to show user’s location on a map.

Vue Native Geolocation (Part One)

Photo of map centered on Colorado
Photo by Katie Drazdauskaite on Unsplash

This Tech Note describes how to enable geolocation functionality in Vue Native (mobile) apps.

This first post deals with configuring Vue Native applications. The second post will show how to implement user location detection and display of position.

Vue Native

First, what is Vue Native? It’s a framework for developing native mobile apps for IOS and Android platforms. It is a wrapper around React Native API’s. Sometimes, this wrapper can get rather thin and underlying code bleeds through to the development task particularly with rather unhelpful error messaging. However, using the Expo.io framework, a lot of effort is abstracted away. But most of the React Native functionality is still directly available with some syntax changes.

Geolocation is obviously important to many applications but particularly for serving information to users such as nearest places of interest or address finding and routing.

Short Form Setup

Setting up a vue native app is quite straightforward. It requires recent versions of node.js & npm to enable a CLI (command line interface) install.

Requirements:

  • Globally installed node >= 6.0
  • Globally installed npm >= 4.0
  • Globally installed Expo CLI

We’re going to use the Expo CLI.

npm install --global expo-cli

Create New Project

We’ll target the ios platform which will provide us an ios simulator (which should auto-launch) as well as physical device handling by scanning a QR code. Android is similar. This will display a basic app to prove everything is running. The following instructions will create the app and install dependencies.

$ vue-native init geoDemo
$ cd geoDemo
$ npm run ios

Anatomy

Vue Native follows a similar structure to web Vue applications. The following illustrates the required Hello World app.

  • Template (views)
  • Script (javascript)
  • Style (obvious)

Replace the content of App.vue with the following:

Template

Each app requires a template and here we are using a container to provide an entity for component styling. The double {{ }} is moustache syntax for interpolation.

<template>
  <view class="container">
    <text class="text-color-primary">{{message}}</text>
  </view>
</template>

Javascript

Similar to web Vue, there is a data function that returns the message content. In this static example, the data is fixed but it does have to be returned from a data function. Note the value of the ‘key’ which is used in the template interpolation inside the ‘moustache’ double curly braces and is replaced by the assigned value.

<script>
export default {
  data: function() {
    return {
      message: "Hello World"
    };
  }
};
</script>

Style

What’s there to say?

<style>
.container {
  flex: 1;
  background-color: white;
  align-items: center;
  justify-content: center;
}
.text-color-primary {
  color: blue;
  font-size: 30;
}
</style>

The second part of this TechNote will code the user location.

Serial read Maxbotix with C++

Code Repo: https://github.com/ravenOSS/maxPhoton5Mqqt

Given the popularity and widespread use of Maxbotix sensors, it would seem that this topic would be well covered but it has been difficult to find examples. Until recently, the Maxbotix website only dealt with analog and pulse width reading.

Many examples illustrate reading the sensor range data and logging to the serial console. However, most Industrial IoT applications require either the local evaluation of data to initiate an action or transmission of a range (distance) data packet.

In a development effort for tank fluid level measurement, we are working with a Maxbotix XL-MaxSonar 7092 sensor suitable for large target sensing. It has analog, pulse width, and RS232 serial output. Other sensor models have I2C or TTL options.

Most micro-controllers do not support true RS232 (incompatible voltage and signal polarity). Therefore, the RS232 output has to be converted to signals that the controller can digest. For prototyping, we are using the MAX3232 breakout board from Sparkfun. This has two sets of inputs & outputs for TTL and RS232 conversion.

The Particle Photon is used for testing and this has both USB serial (“Serial” in example code) and hardware serial (Serial1).

Some Arduino micros only have one serial port available. A great option is the SoftWareSerial library for an additional port and signal conversion. Configuration sets the TX and RX pins, and true/false for signal conversion. Read the docs for some cautions about using this library.

The sensor provides a range reading in the format of ‘R’ + range (3 digit cm) + carriage return (ASCII 13). An important consideration for network bandwidth was to be able to set the timing of range readings instead of just continuously data streaming.

In the initial pass through some code, Serial1.peek() was tried to check for the beginning of the range reading. If ‘R’ was detected, a read of the data was set using atoi() which ignores any proceeding alpha character and only converts ASCII numeric values. Therefore, the ending ‘\n’ is ignored. This approach only returned one value and failed to provide periodic readings.

The next version read the Serial1 stream and tested for ‘R’. parseInt() is used on the stream class to extract ASCII numeric data and convert to an integer range.

This appears to work except that the sensor appears to buffer range readings because after the wait delay, a lot of debug messages are seen followed by a range reading. A Serial1.flush() failed to clear the sensor buffer.

char inChar; // type for data read
char buf[5]; // array to store range data - not used
int range = 0;   // type declaration
char inByte; // not used

void setup()
{
  Serial.begin(57600);
  while (!Serial)
  {
    ; // wait for serial port to connect.
  }
  Serial.println("Serial open");
  Serial1.begin(9600, SERIAL_8N1); // Default but set explicitly anyway
  while (!Serial1)
  {
    ; // wait for Serial1 port to connect.
  }
  Serial.println("Sensor connected");
}

void loop()
{
  range = maxRead();
  Serial.print("Range: ");
  Serial.println(range);
  delay(5000);
}

int maxRead()
{
   Serial1.flush()
   while (Serial1.available() > 0)
  {
    inChar = Serial1.read();
    if (inChar == 'R')
    {
      Serial.println("Got R");
      range = Serial1.parseInt();
      Serial.println(range);
    }
  }
  return range;
}
#include "application.h"

/*
 * Project maxPhoton5
 * Description: Interface Maxbotix serial sensor to Particle Photon or Arduino compatible
 * Author: David Richards / ravenIoT LLC
 * Date: May 22, 2019
 */

// Using hardware UART on Photon, Electron, Fio
// Maxbotix 7092 sensor with RS232 conversion to TTL with MAX3232

void setup();
void loop();
uint16_t maxRead();

void setup()
{
  Serial.begin(57600);
  while (!Serial)
  {
    ; // wait for serial port to connect.
  }
  Serial.println("Serial open");
  // set the data rate from the sensor
  Serial1.begin(9600, SERIAL_8N1); // Default but set explicitly anyway
  while (!Serial1)
  {
    ; // wait for Serial1 port to connect.
  }
  Serial.println("Sensor connected");
}

void loop()
{
  delay(100);
  uint16_t range = maxRead();
  Serial.print("Range: ");
  Serial.println(range);
  delay(1000);
}

uint16_t maxRead() // should not have to declare data type again
{
char inChar;                        // type for data read
const uint8_t length = 3;           // number of ascii numeric characters in sensor data
char charArray[length];             // array to store range data
uint8_t i = 0;                      // initialize counter

  while (Serial1.available()) {
    inChar = Serial1.read();        // continuously read sensor input in while loop
    if (inChar == 'R') {            // test if char == R for beginning data
      Serial.println("Got an R");   // could use decimal 82 for R
      while (i < length) {
        charArray[i] = Serial1.read(); // assign input char to charArray index
        Serial.print("char: ");
        Serial.println(charArray[i]);
        i++;
      }
    }
  }

  for (i = 0; i < 3; i++){          // Just to print the range data to console
  Serial.print("charArray: ");
  Serial.println(charArray[i]);
  }
  return atoi(charArray);            // extract integer range from char charArray
}

Timestamped Dataset for MongoDB Testing

To enable data extraction from mongoDB, I’ve created a simple tool to load a mongo collection with timestamped data.
Moment.js (momentjs.com) provides the timing tool. Hard-coded are the start and end dates for the dataset. The time increment of the data can also be defined.
Too fine a time interval for a given window of time may result in an unnecessarily large collection size.
The parameters for the min – max random data can also be changed to suit testing needs.
Presently tested with mongoDB 3.4, it should be updated for 3.6. It would be interesting to use Luxon for dates and times in the update.
Also, just for demo, dotenv-expand has been used for application variables.
Just go here for the code:
https://github.com/ravenOSS/insertPlotDataSet