/* Copyright Sebastian Haas <sebastian@sebastianhaas.info>. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
//-----------------------------------------------------------------------------
// CAN-Object
var can = require('./build/Release/can');
var buffer = require('buffer');
/**
* @method createRawChannel
* @param channel {string} Channel name (e.g. vcan0)
* @param timestamps {bool} Whether or not timestamps shall be generated when reading a message
* @param protocol {integer} optionally provide another default protocol value (default is CAN_RAW)
* @return {RawChannel} a new channel object or exception
* @for exports
*/
exports.createRawChannel = function(channel, timestamps, protocol)
{
return new can.RawChannel(channel, timestamps, protocol, false);
}
/**
* @method createRawChannel
* @param channel {string} Channel name (e.g. vcan0)
* @param options {dict} list of options (timestamps, protocol, non_block_send)
* @return {RawChannel} a new channel object or exception
* @for exports
*/
exports.createRawChannelWithOptions = function(channel, options)
{
if (options === undefined) options = {}
if (options.timestamps === undefined) options.timestamps = false;
if (options.protocol === undefined) options.protocol = 1; /* CAN RAW */
if (options.non_block_send === undefined) options.non_block_send = false;
return new can.RawChannel(channel, options.timestamps, options.protocol, options.non_block_send);
}
//-----------------------------------------------------------------------------
/**
* The Signals modules provides an interface to access the values/signals
* encoded in CAN messages.
* @module Signals
*/
var _signals = require('./build/Release/can_signals');
var kcd = require('./parse_kcd');
/**
* The actual signal.
* @class Signal
*/
function Signal(desc)
{
/**
* Symbolic name
* @attribute name
* @final
*/
this.name = desc['name'];
this.spn = desc['spn'];
this.bitOffset = desc['bitOffset'];
this.bitLength = desc['bitLength'];
this.endianess = desc['endianess'];
this.type = desc['type'];
this.intercept = desc['intercept'];
this.slope = desc['slope'];
this.minValue = desc['minValue'];
this.maxValue = desc['maxValue'];
this.unit = desc['unit'];
/**
* Label set for defined states of the signal.
*/
this.labels = desc['labels'];
/**
* this will allow triggering on mux'ed message ids.
*/
this.muxGroup = [ desc['mux'] ];
/**
* Current value
*
* @attribute value
* @final
*/
this.value = null;
this.changelisteners = [];
this.updateListeners = [];
}
/**
* Keep track of listeners who want to be notified if this signal changes
* @method onChange
* @param listener JS callback to get notification
* @for Signal
*/
Signal.prototype.onChange = function(listener) {
this.changelisteners.push(listener);
return listener;
}
/**
* Keep track of listeners who want to be notified if this signal updates
* @method onUpdate
* @param listener JS callback to get notification
* @for Signal
*/
Signal.prototype.onUpdate = function(listener) {
this.updateListeners.push(listener);
return listener;
}
/**
* Remove listener from signal onChange and/or onUpdate
* @method removeListener
* @param listener to be removed
* @for Signal
*/
Signal.prototype.removeListener = function(listener) {
var idx = this.changelisteners.indexOf(listener);
if (idx >= 0) this.changelisteners.splice(idx, 1);
idx = this.updateListeners.indexOf(listener);
if (idx >= 0) this.updateListeners.splice(idx, 1);
}
/**
* Set new value of this signal. Any local registered clients will
* receive a notification. Please note, no CAN message is actually
* send to the bus (@see DatabaseServer::send)
* @method update
* @param newValue {bool|double|integer} New value to set
* @for Signal
*/
Signal.prototype.update = function(newValue) {
if (newValue > this.maxValue) {
console.error("ERROR : " + this.name + " value= " + newValue
+ " is outof bounds > " + this.maxValue);
} else if (newValue < this.minValue) {
console.error("ERROR : " + this.name + " value= " + newValue
+ " is outof bounds < " + this.minValue);
}
var changed = this.value !== newValue;
this.value = newValue;
// Update all updateListeners, that the signal updated
for (f in this.updateListeners) {
this.updateListeners[f](this);
}
// Nothing changed
if (!changed) return;
// Update all changelisteners, that the signal changed
for (f in this.changelisteners) {
this.changelisteners[f](this);
}
}
//-----------------------------------------------------------------------------
/**
* Just a container to keep the Signals.
* @class Message
*/
function Message(desc)
{
/**
* CAN identifier
* @attribute id
* @final
*/
this.id = desc.id;
/**
* Extended Frame Format used
* @attribute ext
* @final
*/
this.ext = desc.ext;
/**
* Symbolic name
* @attribute name
* @final
*/
this.name = desc.name;
/**
* Length in bytes of resulting CAN message
*
* @attribute len
* @final
*/
this.len = desc.length;
/**
* This is the time frame that the message gets generated
*
* @attribute interval
* @final
*/
this.interval = desc.interval;
/**
* This tells us the message is mutliplexed.
*
* @attribute muxed
* @final
*/
this.muxed = desc.muxed;
/**
* Multiplexor parameter (just one supported right now).
*
* @attribute mux
* @final
*/
this.mux = desc.mux;
/**
* Named information to inform that the frame is CAN_FD format .
* @attribute Boolean
* @final
*/
this.canfd = desc.canfd;
/**
* Named array of signals within this message. Accessible via index and name.
* @attribute {Signal} signals
* @final
*/
this.signals = [];
for (i in desc['signals']) {
var s = desc['signals'][i];
if (this.signals[s.name] && this.signals[s.name].muxGroup) {
this.signals[s.name].muxGroup.push(s.mux);
} else {
this.signals[s.name] = new Signal(s);
}
}
}
//-----------------------------------------------------------------------------
/**
* A DatabaseService is usually generated once per bus to collect signals
* coded in the CAN messages according a DB description.
* @class DatabaseService
* @constructor DatabaseService
* @param channel RAW channel
* @param db_desc Set of rules to decode/encode signals (@parse_kcd.js)
* @return a new DatabaseService
* @for DatabaseService
*/
function DatabaseService(channel, db_desc) {
this.channel = channel;
/**
* Named array of known messages. Accessible via index and name.
* @attribute {Message} messages
*/
this.messages = [];
for (i in db_desc['messages']) {
var m = db_desc['messages'][i];
var id = m.id | (m.ext ? 1 : 0) << 31;
var nm = new Message(m);
this.messages[id] = nm;
this.messages[m.name] = nm;
}
// Subscribe to any incoming messages
channel.addListener("onMessage", this.onMessage, this);
}
// Callback for incoming messages
DatabaseService.prototype.onMessage = function (msg) {
if (msg == undefined)
return;
// RTR (Remote-Transmit-Request) dont have payload
if (msg.rtr)
return;
id = msg.id | (msg.ext ? 1 : 0) << 31;
var m = this.messages[id];
if (!m) {
return;
}
if (m.muxed) {
b_mux = _signals.decode_signal(msg.data, m.mux.offset, m.mux.length, true, false);
var mux_count = b_mux[0] + (b_mux[1] << 32)
}
// Let the C-Portition extract and convert the signal
for (i in m.signals) {
var s = m.signals[i];
if (s.value === undefined)
continue;
// if this is a mux signal and the muxor isnt in my list...
if (m.muxed && s.muxGroup.indexOf(mux_count) == -1) {
continue;
}
var ret = _signals.decode_signal(msg.data, s.bitOffset, s.bitLength,
s.endianess == 'little', s.type == 'signed');
var val = ret[0] + (ret[1] << 32)
if (s.slope)
val *= s.slope;
if (s.intercept)
val += s.intercept;
s.update(val);
}
}
/**
* Construct a CAN message and encode all related signals according
* the rules. Finally send the message to the bus.
* @method send
* @param msg_name Name of the message to generate (indicate mux by append .MUX_VALUE in hex)
* @for DatabaseService
*/
DatabaseService.prototype.send = function (msg_name) {
var args = msg_name.split("."); // allow for mux'ed messages sent.
var m = this.messages[args[0]];
var mux = (args.length > 1) ? args[1] : undefined;
if (!m)
throw msg_name + " not defined";
var canmsg = {
id: m.id,
ext: m.ext,
rtr: false,
data : (m.len > 0 && m.len < 64) ? Buffer.alloc(m.len) : Buffer.alloc(64) // for CANFD data buffer 64 bytes
};
canmsg.data.fill(0); // should be 0xFF for j1939 message def.
if (mux)
_signals.encode_signal(canmsg.data, m.mux.offset, m.mux.length, true, false, parseInt(mux, 16))
for (i in m.signals) {
var s = m.signals[i];
if (s.value == undefined)
continue;
if (mux) {
if (s.muxGroup.indexOf(parseInt(mux, 16)) === -1) {
continue;
}
}
var val = s.value;
// Apply factor/intercept and convert to Integer
if (s.intercept)
val -= s.intercept;
if (s.slope)
val /= s.slope;
if (typeof(val) == 'double')
val = parseInt(Math.round(val));
if (m.len == 0) {
return;
}
var word1 = val & 0xFFFFFFFF
var word2 = 0
// shift doesn't work above 32 bit, only do this if required to save cycles
if (val > 0xFFFFFFFF)
word2 = (val / Math.pow(2, 32))
_signals.encode_signal(canmsg.data, s.bitOffset, s.bitLength,
s.endianess == 'little', s.type == 'signed', word1, word2 );
}
this.channel.send(canmsg);
}
/**
* @method parseNetworkDescription
* @param file {string} Path to KCD file to parse
* @return DB description to be used in DatabaseService
* @for exports
*/
exports.parseNetworkDescription = kcd.parseKcdFile;
exports.DatabaseService = DatabaseService;