/main.js |
@@ -1,66 +1,92 @@ |
#!/usr/bin/env nodejs |
/////////////////////////////////////////////////////////////////////////// |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2018 - License: GNU GPLv3 // |
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
// rights of fair usage, the disclaimer and warranty conditions. // |
/////////////////////////////////////////////////////////////////////////// |
|
// Global package definitions. |
const Gpio = require('onoff').Gpio |
const mqtt = require('mqtt') |
const YAML = require('yamljs') |
const winston = require('winston') |
const SerialPort = require('serialport') |
|
var serialport = require("serialport") |
var SerialPort = serialport.SerialPort |
// Local package definitions. |
const decoder = require('./lib/decoder.js') |
|
var SerialPort = require('serialport'); |
var port = new SerialPort('/dev/serial0', { |
baudRate: 115200 |
}); |
|
// Load configuration file. |
const config = YAML.load('config.yml') |
|
// Set up logger. |
winston.add(winston.transports.File, {filename: config.log}) |
winston.add(winston.transports.File, { |
filename: config.log |
}) |
|
// Initiate connection to MQTT. |
const client = mqtt.connect(config.mqtt.url, {queueQoSZero: false}) |
const mqttClient = mqtt.connect(config.mqtt.url) |
|
// Set up serial port. |
var port = new SerialPort(config.serial_device, { |
// Roomba 4xx and Dirt Dog: 57600 |
// Roomba 5xx and 7xx: 115200 |
baudRate: 115200, |
dataBits: 8, |
parity: 'none', |
stopBits: 1, |
flowControl: false |
}) |
|
// Set up sensor packet assembler. |
port.on('data', AssembleSensorPacket); |
|
// Set up GPIO wake pin. |
const wakeGPIO = new Gpio(config.wake, 'out') |
// Wake up Roomba. |
RoombaWake(wakeGPIO, function () { |
// Switch to safe mode. |
RoombaSafe(function () { |
// Poll sensors periodically. |
setInterval(function () { |
port.write(new Buffer(COMMAND_SEQUENCES['query_all_sensors'])) |
}, config.sensor_poll_interval) |
}) |
}) |
|
client.on('connect', function () { |
// Subscribe to configured topic when client connects and start polling sensors. |
mqttClient.on('connect', function () { |
winston.info('Connected to MQTT server') |
client.subscribe(config.mqtt.topic) |
mqttClient.subscribe(config.mqtt.topic, function (error) { |
if (error) { |
winston.error('Unable to subscribe to MQTT topic') |
} |
winston.info("Subscribed to MQTT topic " + config.mqtt.topic) |
}) |
}) |
|
client.on('message', function (topic, message) { |
if(message.length === 0) |
return; |
// Execute command when message is received on configured topic. |
mqttClient.on('message', function (topic, message) { |
if (message.length === 0) |
return |
|
// Remove any retained message. |
client.publish(topic, "", {retain: true}) |
|
message = message.toString() |
|
winston.debug('Received message: ' + message) |
|
if(!(message in config.ROOMBA)) { |
winston.warn('Unknown Roomba command: ' + message) |
return; |
} |
// Skip commands that do not figure in the command sequence table. |
if (!(message in COMMAND_SEQUENCES)) |
return |
|
winston.info('Executing Roomba command: ' + message) |
|
// Wake up Roomba. |
RoombaWake(wakeGPIO) |
|
// Execute Roomba command. |
RoombaExecute(message) |
// Execute Roomba command in safe mode. |
RoombaSafe(function () { |
port.write(new Buffer(COMMAND_SEQUENCES[message])) |
}) |
}) |
|
/* |
* Wake Roomba device. |
* Sequence: |
* 1. Set DD to HIGH |
* 2. Wait 100ms |
@@ -68,41 +94,89 @@ |
* 4. Wait 500ms |
* 5. Set DD to HIGH |
*/ |
function RoombaWake(gpio) { |
function RoombaWake(gpio, callback) { |
gpio.write(1, err => { |
if(err) { |
if (err) { |
winston.error('Could not wake Roomba!') |
return; |
return |
} |
|
setTimeout(function() { |
gpio.write(0, function(err) { |
if(err) { |
winston.error('Could not wake Roomba!') |
return; |
} |
setTimeout(function() { |
gpio.write(1, function(err) { |
if(err) { |
winston.error('Could not wake Roomba!') |
return; |
} |
}) |
}, 500) |
|
setTimeout(function () { |
gpio.write(0, function (err) { |
if (err) { |
winston.error('Could not wake Roomba!') |
return |
} |
setTimeout(function () { |
gpio.write(1, function (err) { |
if (err) { |
winston.error('Could not wake Roomba!') |
return |
} |
callback() |
}) |
}, 500) |
}) |
}, 100) |
}) |
} |
|
function RoombaExecute(message) { |
/* |
* Put Roomba in safe mode. |
*/ |
function RoombaSafe(callback) { |
setTimeout(function () { |
port.write(new Buffer(config.ROOMBA['start'])); |
port.write(new Buffer(COMMAND_SEQUENCES['start'])) |
setTimeout(function () { |
port.write(new Buffer(config.ROOMBA['safe'])); |
setTimeout(function () { |
port.write(new Buffer(config.ROOMBA[message])); |
}, 500) |
port.write(new Buffer(COMMAND_SEQUENCES['safe'])) |
setTimeout(callback, 500) |
}, 30) |
}, 30) |
} |
|
/* |
* Build the sensor buffer as the data is read from the serial port. |
*/ |
var packetBuffer = null |
|
function AssembleSensorPacket(data) { |
if (packetBuffer) { |
data = Buffer.concat( |
[packetBuffer, data], packetBuffer.length + data.length) |
} |
|
if (data.length >= decoder.SENSOR_PACKET_SIZE) { |
var packet = data.slice(0, decoder.SENSOR_PACKET_SIZE) |
|
decoder.decode_all_sensors(packet, function (result, position, error) { |
if (error) { |
winston.error('Packet assembly failed with error:' + error) |
return |
} |
|
// Push the packet onto MQTT. |
mqttClient.publish(config.mqtt.topic, JSON.stringify(result)) |
}) |
|
data = data.slice(decoder.SENSOR_PACKET_SIZE) |
} |
|
if (data.length > 0) { |
packetBuffer = data |
return |
} |
|
packetBuffer = null |
} |
|
const COMMAND_SEQUENCES = { |
safe: [131], |
start: [128], |
spot: [134], |
clean: [135], |
dock: [143], |
query_all_sensors: [142, 100], |
stream_all_sensors: [148, 1, 100], |
stream_sensors_off: [150, 0], |
stream_sensors_on: [150, 1] |
} |