roomba-iot – Blame information for rev 4

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 #!/usr/bin/env nodejs
4 office 2 ///////////////////////////////////////////////////////////////////////////
1 office 3 // Copyright (C) Wizardry and Steamworks 2018 - License: GNU GPLv3 //
4 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
5 // rights of fair usage, the disclaimer and warranty conditions. //
6 ///////////////////////////////////////////////////////////////////////////
7  
2 office 8 // Global package definitions.
1 office 9 const Gpio = require('onoff').Gpio
10 const mqtt = require('mqtt')
11 const YAML = require('yamljs')
12 const winston = require('winston')
2 office 13 const SerialPort = require('serialport')
1 office 14  
2 office 15 // Local package definitions.
16 const decoder = require('./lib/decoder.js')
1 office 17  
18 // Load configuration file.
19 const config = YAML.load('config.yml')
20  
21 // Set up logger.
4 office 22 const logger = winston.createLogger({
23 transports: [
24 new winston.transports.Console(),
25 new winston.transports.File({ filename: config.log })
26 ]
27 });
1 office 28  
29 // Initiate connection to MQTT.
2 office 30 const mqttClient = mqtt.connect(config.mqtt.url)
1 office 31  
2 office 32 // Set up serial port.
33 var port = new SerialPort(config.serial_device, {
34 // Roomba 4xx and Dirt Dog: 57600
35 // Roomba 5xx and 7xx: 115200
36 baudRate: 115200,
37 dataBits: 8,
38 parity: 'none',
39 stopBits: 1,
40 flowControl: false
41 })
42  
43 // Set up sensor packet assembler.
4 office 44 port.on('data', PublishSensorPacket);
2 office 45  
1 office 46 // Set up GPIO wake pin.
47 const wakeGPIO = new Gpio(config.wake, 'out')
2 office 48 // Wake up Roomba.
49 RoombaWake(wakeGPIO, function () {
50 // Switch to safe mode.
51 RoombaSafe(function () {
52 // Poll sensors periodically.
53 setInterval(function () {
4 office 54 port.write(new Buffer.from(COMMAND_SEQUENCES['query_all_sensors']))
2 office 55 }, config.sensor_poll_interval)
56 })
57 })
1 office 58  
2 office 59 // Subscribe to configured topic when client connects and start polling sensors.
60 mqttClient.on('connect', function () {
4 office 61 logger.info('Connected to MQTT server')
2 office 62 mqttClient.subscribe(config.mqtt.topic, function (error) {
63 if (error) {
4 office 64 logger.error('Unable to subscribe to MQTT topic')
2 office 65 }
4 office 66 logger.info("Subscribed to MQTT topic " + config.mqtt.topic)
2 office 67 })
1 office 68 })
69  
2 office 70 // Execute command when message is received on configured topic.
71 mqttClient.on('message', function (topic, message) {
72 if (message.length === 0)
73 return
1 office 74  
75 message = message.toString()
76  
4 office 77 logger.debug('Received message: ' + message)
1 office 78  
2 office 79 // Skip commands that do not figure in the command sequence table.
80 if (!(message in COMMAND_SEQUENCES))
81 return
1 office 82  
4 office 83 logger.info('Executing Roomba command: ' + message)
1 office 84  
2 office 85 // Execute Roomba command in safe mode.
86 RoombaSafe(function () {
4 office 87 port.write(new Buffer.from(COMMAND_SEQUENCES[message]))
2 office 88 })
1 office 89 })
90  
4 office 91 mqttClient.on('reconnect', () => {
92 logger.info('Reconnecting to MQTT server...')
93 })
94  
1 office 95 /*
2 office 96 * Wake Roomba device.
1 office 97 * Sequence:
98 * 1. Set DD to HIGH
99 * 2. Wait 100ms
100 * 3. Set DD to LOW
101 * 4. Wait 500ms
102 * 5. Set DD to HIGH
103 */
2 office 104 function RoombaWake(gpio, callback) {
1 office 105 gpio.write(1, err => {
2 office 106 if (err) {
4 office 107 logger.error('Could not wake Roomba!')
2 office 108 return
1 office 109 }
2 office 110  
111 setTimeout(function () {
112 gpio.write(0, function (err) {
113 if (err) {
4 office 114 logger.error('Could not wake Roomba!')
2 office 115 return
116 }
117 setTimeout(function () {
118 gpio.write(1, function (err) {
119 if (err) {
4 office 120 logger.error('Could not wake Roomba!')
2 office 121 return
122 }
123 callback()
124 })
125 }, 500)
1 office 126 })
127 }, 100)
128 })
129 }
130  
2 office 131 /*
132 * Put Roomba in safe mode.
133 */
134 function RoombaSafe(callback) {
1 office 135 setTimeout(function () {
4 office 136 port.write(new Buffer.from(COMMAND_SEQUENCES['start']))
1 office 137 setTimeout(function () {
4 office 138 port.write(new Buffer.from(COMMAND_SEQUENCES['safe']))
2 office 139 setTimeout(callback, 500)
3 office 140 }, 100)
141 }, 100)
1 office 142 }
143  
2 office 144 /*
145 * Build the sensor buffer as the data is read from the serial port.
146 */
147 var packetBuffer = null
148  
4 office 149 function PublishSensorPacket(data) {
2 office 150 if (packetBuffer) {
151 data = Buffer.concat(
152 [packetBuffer, data], packetBuffer.length + data.length)
153 }
154  
155 if (data.length >= decoder.SENSOR_PACKET_SIZE) {
156 var packet = data.slice(0, decoder.SENSOR_PACKET_SIZE)
157  
158 decoder.decode_all_sensors(packet, function (result, position, error) {
159 if (error) {
4 office 160 logger.error('Packet assembly failed with error:' + error)
2 office 161 return
162 }
163  
164 // Push the packet onto MQTT.
165 mqttClient.publish(config.mqtt.topic, JSON.stringify(result))
166 })
167  
168 data = data.slice(decoder.SENSOR_PACKET_SIZE)
169 }
170  
171 if (data.length > 0) {
172 packetBuffer = data
173 return
174 }
175  
176 packetBuffer = null
177 }
178  
179 const COMMAND_SEQUENCES = {
180 safe: [131],
181 start: [128],
182 spot: [134],
183 clean: [135],
184 dock: [143],
185 query_all_sensors: [142, 100],
186 stream_all_sensors: [148, 1, 100],
187 stream_sensors_off: [150, 0],
188 stream_sensors_on: [150, 1]
189 }