roomba-iot – Rev 4
?pathlinks?
#!/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')
// Local package definitions.
const decoder = require('./lib/decoder.js')
// Load configuration file.
const config = YAML.load('config.yml')
// Set up logger.
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: config.log })
]
});
// Initiate connection to MQTT.
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', PublishSensorPacket);
// 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.from(COMMAND_SEQUENCES['query_all_sensors']))
}, config.sensor_poll_interval)
})
})
// Subscribe to configured topic when client connects and start polling sensors.
mqttClient.on('connect', function () {
logger.info('Connected to MQTT server')
mqttClient.subscribe(config.mqtt.topic, function (error) {
if (error) {
logger.error('Unable to subscribe to MQTT topic')
}
logger.info("Subscribed to MQTT topic " + config.mqtt.topic)
})
})
// Execute command when message is received on configured topic.
mqttClient.on('message', function (topic, message) {
if (message.length === 0)
return
message = message.toString()
logger.debug('Received message: ' + message)
// Skip commands that do not figure in the command sequence table.
if (!(message in COMMAND_SEQUENCES))
return
logger.info('Executing Roomba command: ' + message)
// Execute Roomba command in safe mode.
RoombaSafe(function () {
port.write(new Buffer.from(COMMAND_SEQUENCES[message]))
})
})
mqttClient.on('reconnect', () => {
logger.info('Reconnecting to MQTT server...')
})
/*
* Wake Roomba device.
* Sequence:
* 1. Set DD to HIGH
* 2. Wait 100ms
* 3. Set DD to LOW
* 4. Wait 500ms
* 5. Set DD to HIGH
*/
function RoombaWake(gpio, callback) {
gpio.write(1, err => {
if (err) {
logger.error('Could not wake Roomba!')
return
}
setTimeout(function () {
gpio.write(0, function (err) {
if (err) {
logger.error('Could not wake Roomba!')
return
}
setTimeout(function () {
gpio.write(1, function (err) {
if (err) {
logger.error('Could not wake Roomba!')
return
}
callback()
})
}, 500)
})
}, 100)
})
}
/*
* Put Roomba in safe mode.
*/
function RoombaSafe(callback) {
setTimeout(function () {
port.write(new Buffer.from(COMMAND_SEQUENCES['start']))
setTimeout(function () {
port.write(new Buffer.from(COMMAND_SEQUENCES['safe']))
setTimeout(callback, 500)
}, 100)
}, 100)
}
/*
* Build the sensor buffer as the data is read from the serial port.
*/
var packetBuffer = null
function PublishSensorPacket(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) {
logger.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]
}
Generated by GNU Enscript 1.6.5.90.