roomba-iot – Blame information for rev 4
?pathlinks?
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 | } |