Examples

Data Manipulation and Events

Example - Create the sum of analog1 and analog2 values and send the new value as cp1. Also monitor the sum against predefined thresholds. If a threshold is exceeded, dispatch an event. Note that it is also good practice to check the analog values exist (and are a ‘number’ type) before trying to use them, or the script will fail.

load('senquip.js');
let ALARM_THRESHOLD = 55;
let WARN_THRESHOLD = 20;

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);
  if ((typeof obj.analog1 === "number") && (typeof obj.analog2 === "number")) {
    let sum = obj.analog1 + obj.analog2;
    SQ.dispatch(1, sum);
    if (sum >= ALARM_THRESHOLD) {
      SQ.dispatch_event(1, SQ.ALARM, "Sum critical");
    } else if (sum >= WARN_THRESHOLD) {
      SQ.dispatch_event(1, SQ.WARNING, "Sum high");
    }
  } else {
    SQ.dispatch_event(1, SQ.INFO, "No Data");
  }
}, null);

Example - Scale and offset values current1 and current2, and send them as the new values cp1 and cp5 with varying precision.

load('senquip.js');

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);
  SQ.dispatch_double(1, 42.1*obj.current1 + 1.51, 0); // No decimal
  SQ.dispatch_double(5, -3.1416*obj.current2 + 9, 3); // 3 decimal places
}, null);

Filtering & Persistent Variables

Example - Implementation an exponential moving average across consecutive measurement cycles. Highlights the use of global variables to store information between measurement cycles.

load('senquip.js');
let filtered_value = 0;

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);
  let alpha = 0.3;
  let new_sample = obj.current1;
  filtered_value = (alpha * new_sample) + (1 - alpha)*filtered_value;
  SQ.dispatch(1, filtered_value);
}, null);

Note

The above example using global variables will only work if the device is set to ‘Always On’. Information in global variables is lost when the device sleeps or resets.

Example - Persist variables when the device goes to sleep.

Coming soon!

Parsing CAN Data

Example - Look for a specific PGN, parse the first 2 bytes of CAN data from Hex format, convert to a 16-bit signed value, then apply a fixed scale and offset to the result.

load('senquip.js');

// Function to convert a 16-bit unsigned integer to 16-bit signed value
function int16(x) {
  if (x > 32767) {x = x - 65536;}
  return x;
}

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);
  for (let i = 0; i < obj.can1.length; i++) {

    // Look for the specific PGN:
    if (obj.can1[i].id === 0x18FF1CF2) {

      // Extract the first 4 Hex characters (2 bytes):
      let d = SQ.parse(obj.can1[i].data, 0, 4, 16);

      // Convert to a signed 16-bit value and scale/offset the result:
      SQ.dispatch_double(1, int16(d) * 0.125 + 4.5, 1);

      // Extract the next 4 Hex characters and reverse the byte order:
      let e = SQ.parse(obj.can1[i].data, 4, 8, -16);

      // Keep result as an unsigned number and scale:
      SQ.dispatch_double(2, e * 0.5, 1);
    }
  }
}, null);

J1939 Fault Codes

Example - Determine when a J1939 Fault code is active on CAN1 using some funky byte manipulation.

load('senquip.js');

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);

  for (let i = 0; i < obj.can1.length; i++) {
    // Look for the fault code PGN from any SA:
    if ((obj.can1[i].id>>8) === 0x18FECA) {
      // Decode DTC according to Conversion Method 4
      let byte34 = SQ.parse(obj.can1[i].data, 4, 4, 16, SQ.U16);
      let byte5 = SQ.parse(obj.can1[i].data, 8, 2, 16, SQ.U8);
      let byte6 = SQ.parse(obj.can1[i].data, 10, 2, 16, SQ.U8);
      let spn = byte34 | (byte5 & 0xE0)<<11;
      let fmi = byte5 & 0x1F;
      let oc = byte6 & 0x7F;
      if (spn !== 0) {
        let s = "DTC SPN: " + JSON.stringify(spn) + " FMI: " + JSON.stringify(fmi);
        SQ.dispatch_event(1, SQ.ALARM, s);
      }
    }
  }
}, null);

Transmit CAN Data

Example - Regularly send a CAN message using a timer, containing measurements from the device.

load('senquip.js');
load('api_timer.js');

let en_tx = false;
let can_msg = "01234567";

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);
  if (typeof obj.analog1 === "number") {
    let a = Math.floor(obj.analog1*100);
    can_msg = chr(f&0xFF) + chr((f>>8)&0xFF) + chr((f>>16)&0xFF);
    en_tx = true;
  } else {
    en_tx = false;
  }
}, null);

// Set up a repeating timer to handle the CAN transmit:
Timer.set(1000, Timer.REPEAT, function() {
  // Do nothing if tx is not enabled:
  if (en_tx === false) return;

  // Send an extended message:
  CAN.tx(1, 0x18FB1055, can_msg, can_msg.length, CAN.EXT);
}, null);

Use of Triggers

Example - Set up a trigger handler to respond to button presses from the Senquip Portal.

load('senquip.js');
load('api_serial.js');

SQ.set_trigger_handler(function(tp) {
  if (tp === 1) { SQ.set_output(1, SQ.ON, 5); }  // Turn on Output 1 for 5s
  if (tp === 2) { SQ.set_output(1, SQ.OFF, 0); } // Turn Output 1 off forever
  if (tp === 3) {
    let s = "HELLO WORLD!";
    SERIAL.write(1, s, s.length);  // Send string over serial port (RS232 or RS485)
  }
  if (tp === 4) { SERIAL.write(1, "\x48\x45\x58", 3); } // Send 3 bytes in HEX format
}, null);

Use of Timers

Example - Set up a trigger handler to immediately send one serial message, and then send a second message one second later.

load('senquip.js');
load('api_timer.js');
load('api_serial.js');

SQ.set_trigger_handler(function(tp) {
  if (tp === 1) {
    // When Portal button #1 is pressed, immediately send 1st serial string
    let s1 = "TURN ON";
    SERIAL.write(1, s1, s1.length);
    // Set a non-repeating timer for 1000 ms, which will call the inline function().
    Timer.set(1000, 0, function() {
      // After one second, send the 2nd serial string
      let s2 = "TURN OFF";
      SERIAL.write(1, s2, s2.length);
    }, null);
  }
}, null);

String Enumerations

Example - Send a string enumeration based on an analog voltage measurement. It also gracefully handles the case where ‘analog1’ does not exist in the data message, in which case ‘typeof obj.analog1’ would have the value ‘undefined’ (rather than ‘number’).

load('senquip.js');

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);
  let s = "No Signal";
  if (typeof obj.analog1 === "number") {
    if (obj.analog1 >= 4.5) { s = "Error"; }
    else if (obj.analog1 >= 3.5) { s = "High"; }
    else if (obj.analog1 >= 2.5) { s = "Normal"; }
    else { s = "Low"; }
  }
  SQ.dispatch(1, s);
}, null);

Custom HTTP Message

Example - Send a custom HTTP POST message.

load('senquip.js');
load('api_endpoint.js');

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);
  let out = {
    id: obj.deviceid,
    time: obj.ts,
    flow_rate: obj.current1 * 1000
  };
  HTTP.post(JSON.stringify(out), "Content-Type: application/json\r\n");
}, null);

Custom Settings

Example - Retrieval and use of custom settings from a script. The example also dispatches the settings to the Portal so a record of the current value is kept. This may be desirable for debugging, traceability or reporting purposes.

load('senquip.js');
load('api_config.js');

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);

  // Get the settings:
  let num_threshold = Cfg.get('script.num1');
  let str_setting = Cfg.get('script.str1');

  // Compare measurement against the custom setting:
  if (obj.analog1 > num_threshold) { SQ.set_output(1, SQ.ON, 0); }

  // Send the custom settings to the Portal for reporting/logging purposes:
  SQ.dispatch(1, num_threshold);
  SQ.dispatch(2, str_setting);
}, null);

Bluetooth

Example - An example of how to transmit and receive GPS data from one device to another using Bluetooth advertisements.

load('senquip.js');

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);

  // If we have a valid GPS position, broadcast this to other devices via BLE
  if (typeof obj.gps_lat !== 'undefined') {
      let ble_data = SQ.encode(obj.gps_lat,SQ.FLOAT) + SQ.encode(obj.gps_lon,SQ.FLOAT);
      BLE.sq_adv(ble_data);
  }

  // Check for BLE data from nearby devices
  if (typeof obj.ble !== 'undefined') {
    for (let i = 0; i < obj.ble.length; i++) {
      // Check the message contains the 'Senquip Manufacturer Specific' header
      let sl = obj.ble[i].data.slice(2, 8);
      if (sl === "ff710a") {
        // We have found a message from another device
        // Extract the lat and long of the other device:
        let lat = SQ.parse(obj.ble[i].data,8,8,16,SQ.FLOAT);
        let lng = SQ.parse(obj.ble[i].data,16,8,16,SQ.FLOAT);
        // Calculated the distance to us:
        let meters = SQ.distance(lat, lng);
        SQ.dispatch(1, meters);
      }
    }
  }
}, null);

Example - Send data via Bluetooth to the Senquip Mobile App.

Note

The Senquip Mobile app is under development, and new features are coming soon. The following example works with App version 1.0.0

load('senquip.js');

let APP_FORMAT_ID = "\x80";

SQ.set_data_handler(function(data) {
  let obj = JSON.parse(data);

  // To send text, the first byte must always be \x80 so the app knows how to interpret the data.
  if (typeof obj.analog1 !== 'undefined') {
    let fuel_level = obj.analog1 * 205;
    let ble_data = APP_FORMAT_ID + "G472 - Fuel Level: " + JSON.stringify(fuel_level);
    BLE.sq_adv(ble_data);
  }

}, null);