Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format5 Support #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ Signal K Node server plugin to provide [RuuviTag](https://tag.ruuvi.com/) enviro

- Temperature
- Humidity
- Preassure
- Pressure
- Acceleration X (Ruuvi dataformat v5 only)
- Acceleration Y (Ruuvi dataformat v5 only)
- Acceleration Z (Ruuvi dataformat v5 only)
- Movement Counter (Ruuvi dataformat v5 only)

Also following sensor meta data is provided:

- RSSI (signal strength)
- Battery voltage (requires RuuviTag to run in [raw mode](https://lab.ruuvi.com/ruuvitag-fw/))
- Battery voltage (requires RuuviTag to run in [raw mode](https://lab.ruuvi.com/ruuvitag-fw/)
or Ruuvi dataformat v5)
- Data Format (Ruuvi dataformat v5 only)
- Measurement Sequence Number (Ruuvi dataformat v5 only)
- TX Power (Ruuvi dataformat v5 only)

### Usage

Expand Down
233 changes: 179 additions & 54 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
const _ = require('lodash')
'use strict';
const PLUGIN_ID = 'signalk-ruuvitag'
const PLUGIN_NAME = 'RuuviTag Plugin (supports dataformat v5)'

const debug = require('debug')(PLUGIN_ID);
const _ = require('lodash')
const Bacon = require('baconjs')

module.exports = function(app) {

let plugin = {};
plugin.id = PLUGIN_ID;
plugin.name = PLUGIN_NAME;
plugin.description = 'Provides environment data from nearby RuuviTag beacons.';
let config = {}
let unsubscribes = []
let ruuviInitialized = false
let ruuviTagsProperty = undefined

const start = (initialConfig) => {
plugin.start = function (initialConfig, restartPlugin) {
app.debug('Plugin started');
config = _.cloneDeep(initialConfig)

if (!ruuviInitialized) {
Expand All @@ -19,7 +29,7 @@ module.exports = function(app) {
const unsubConfig = ruuviTagsProperty.onValue(tags => {
_.each(tags, ({id, dataStream}) => {
if (!config[id]) {
config[id] = {
config[id] = { // seem to be defaults
id: id,
name: id.substring(0, 6),
location: 'inside',
Expand All @@ -39,22 +49,25 @@ module.exports = function(app) {
return Bacon.mergeAll(dataStreams)
})


const unsubData = allTagsDataStream.onValue(data => {
if (data.enabled) {
app.handleMessage('ruuvitag', createDelta(data))
app.handleMessage(PLUGIN_ID, createDelta(data))
}
})

unsubscribes = [unsubData, unsubConfig]
}
} // end plugin.start

const stop = () => {
plugin.stop = function () {
app.debug('Plugin stopped');
_.each(unsubscribes, fn => fn())
unsubscribes = []
}
} // end plugin.stop

plugin.schema = function() {
app.debug('Plugin schema');

const schema = () => {
// iterate through all found RuuviTags and construct schema object
const properties = _.mapValues(config, (c, id) => ({
title: `Tag ${id}`,
type: 'object',
Expand Down Expand Up @@ -86,18 +99,11 @@ module.exports = function(app) {
title: "",
type: "object",
properties
}
}
}
} // end plugin.schema

return {
id: 'ruuvitag',
name: 'RuuviTag Plugin',
description: 'Provides environment data from nearby RuuviTags beacons.',
schema,
start,
stop
}
}
return plugin;
} // end module.exports

const initializeRuuviListener = () => {
const ruuvi = require('node-ruuvitag')
Expand All @@ -117,54 +123,173 @@ const createRuuviData = (config, id, data) => {
name: _.get(config, [id, 'name'], id.substring(0, 6)),
enabled: _.get(config, [id, 'enabled'], false),
location: _.get(config, [id, 'location'], 'inside'),
dataFormat: data.dataFormat, // better include this info, too. Helps to identify invalid data
movementCounter: data.movementCounter, // v5
measurementSequenceNumber: data.measurementSequenceNumber, // v5
humidity: data.humidity,
pressure: data.pressure,
temperature: data.temperature,
accelerationX: data.accelerationX,
accelerationY: data.accelerationY,
accelerationZ: data.accelerationZ,
rssi: data.rssi,
txPower: data.txPower,
battery: data.battery,
raw: !data.eddystoneId
}
}

const performUnitConversions = (data) => {
data.humidity = data.humidity / 100 // 38% -> 0.38
data.temperature = data.temperature + 273.15 // C -> K
data.battery = data.battery / 1000 // mV -> V
if (!data.raw) {
data.pressure = data.pressure * 100 // hPa -> Pa
}
if ( data.dataFormat == 5 ) {
// let's round all values after any conversions
// to 1 digit more than the ruuvi's numerical resolution
if (data.temperature != null) {
data.temperature = _.round(data.temperature + 273.15, 4); // C -> K
}
if (data.humidity != null ) {
data.humidity = _.round(data.humidity / 100, 7); // 38% -> 0.38
}
// if (data.pressure != null) {
//data.pressure = _.round(data.pressure, 1); // Pa
// }
if (data.accelerationX != null ) {
data.accelerationX = _.round(data.accelerationX / 1000, 4); // mG -> G
}
if (data.accelerationY != null ) {
data.accelerationY = _.round(data.accelerationY / 1000, 4); // mG -> G
}
if (data.accelerationZ != null ) {
data.accelerationZ = _.round(data.accelerationZ / 1000, 4); // mG -> G
}
if (data.battery != null) {
data.battery = _.round(data.battery / 1000, 4); // mV -> V
}
// txPower
// movementCounter
// measurementSequenceNumber
}
else if ( data.dataFormat < 5 ) { // legacy formats
data.humidity = data.humidity / 100 // 38% -> 0.38
data.temperature = data.temperature + 273.15 // C -> K
data.battery = data.battery / 1000 // mV -> V
if (!data.raw) {
data.pressure = data.pressure * 100 // hPa -> Pa
}
}
else { // data format not (yet) supported
// now what ?
}
return data
}

const createDelta = (data) => ({
updates: [
{
'$source': 'ruuvitag.' + data.name,
values: [
{
path: `environment.${data.location}.humidity`,
value: _.round(data.humidity, 2)
},
{
path: `environment.${data.location}.temperature`,
value: _.round(data.temperature, 2)
},
{
path: `environment.${data.location}.pressure`,
value: _.round(data.pressure)
},
{
path: `environment.${data.location}.rssi`,
value: _.round(data.rssi)
},
{
path: `environment.${data.location}.battery`,
value: _.round(data.battery)
},
]
const createDelta = (data) => {
if (data.dataFormat == 5 ) return {
// pathes were chosen to match SignalK's definition so that the correct unit
// is displayed in the webinterface of the SignalK Server (->databrowser)
//
// test1 - path not recognized with correct unit by SignalK
// path: `environment.${data.location}.${data.name}.relativeHumidity`,
// test2 - also no correct unit displayed
// path: `environment.${data.location}.${data.name}.electrical.batteries.internal.voltage`,
// test3 - this doesn't seem to work
// meta: { "units": "V",
// "description": "Voltage of Ruuvitag's internal battery" }
// test4 - not yet implemented in SignalK?
// path: `sensors.${data.name}.dataFormat`,
// test5 - this shows the voltage as 'Volts', but it is a different path from all the other ruuvitag data
// path: `electrical.batteries.${data.name}.voltage`,
updates: [
{
source: { label: 'ruuvitag-currently-ignored-label',
src: data.name },
values: [
{
path: `environment.${data.location}.dataFormat`,
value: data.dataFormat
},
{
label: `Humidity ${data.location}`,
path: `environment.${data.location}.relativeHumidity`,
value: data.humidity
},
{
path: `environment.${data.location}.temperature`,
value: data.temperature
},
{
path: `environment.${data.location}.pressure`,
value: data.pressure
},
{
path: `environment.${data.location}.rssi`,
value: data.rssi
},
{
path: `environment.${data.location}.battery`,
value: data.battery,
},
{
path: `environment.${data.location}.accelerationX`,
value: data.accelerationX,
},
{
path: `environment.${data.location}.accelerationY`,
value: data.accelerationY,
},
{
path: `environment.${data.location}.accelerationZ`,
value: data.accelerationZ,
},
{
path: `environment.${data.location}.txPower`,
value: data.txPower,
},
{
path: `environment.${data.location}.movementCounter`,
value: data.movementCounter,
},
{
path: `environment.${data.location}.measurementSequenceNumber`,
value: data.measurementSequenceNumber,
},
]
}
]
}
]
})
else return {
updates: [
{
source: { label: 'ruuvitag-currently-ignored-label',
src: data.name },
values: [
{
label: `Data Format ${data.location}`,
path: `environment.${data.location}.dataFormat`,
value: data.dataFormat
},
{
label: `Humidity ${data.location}`,
path: `environment.${data.location}.humidity`,
value: _.round(data.humidity, 2)
},
{
path: `environment.${data.location}.temperature`,
value: _.round(data.temperature, 2)
},
{
path: `environment.${data.location}.pressure`,
value: _.round(data.pressure)
},
{
path: `environment.${data.location}.rssi`,
value: _.round(data.rssi)
},
{
path: `environment.${data.location}.battery`,
value: _.round(data.battery)
},
]
}
]
}
}
Loading