forked from pwnall/node-sphero-pwn
-
Notifications
You must be signed in to change notification settings - Fork 1
/
discovery.coffee
151 lines (135 loc) · 4.31 KB
/
discovery.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
EventEmitter = require 'events'
noble = require 'noble'
serialport = require 'serialport'
BleChannel = require './ble_channel.coffee'
SerialChannel = require './serial_channel.coffee'
# Agent that discovers spheros.
class DiscoveryClass extends EventEmitter
# Creates a new discovery agent.
constructor: ->
@_serialPorts = null
@_peripherals = null
@reset()
@_started = false
@_bleScanStarted = false
noble.on 'stateChange', @_onBleStateChange.bind(@)
noble.on 'discover', @_onBleDiscover.bind(@)
@_powerState = noble.state
# Starts discovering Bluetooth robots.
#
# @return {DiscoveryClass} this
start: ->
return if @_started
@_started = true
serialport.list @_onSerialPortList.bind(@)
@_onBleStateChange noble.state
@
# Stops discovering Bluetooth robots.
#
# @return {DiscoveryClass} this
stop: ->
return if @_started is false
@_started = false
if @_bleScanStarted is true
@_bleScanStarted = false
noble.stopScanning()
@
# Erases the list of discovered Bluetooth devices.
#
# @return {DiscoveryClass} this
reset: ->
@_serialPorts = {}
@_peripherals = {}
@
# Discovers Bluetooth robots until a desired robot shows up.
#
# @param {String} sourceId the source ID of the communication channel to the
# desired robot
# @return {Promise<Channel>} resolved with a communication channel to the
# desired robot
findChannel: (sourceId) ->
@findChannels([sourceId])
.then (channels) ->
channels[sourceId]
# Discovers Bluetooth robots until a set of desired robots show up.
#
# @param {Array<String>} sourceIds the source IDs of the communication
# channels to the desired robots
# @return {Promise<Object<String, Channel>>} resolved with an object that
# maps source IDs to communication channels to the desired robots
findChannels: (sourceIds) ->
channelsLeft = sourceIds.length
channels = {}
for sourceId in sourceIds
channels[sourceId] = null
new Promise (resolve, reject) =>
@stop()
@reset()
onChannel = (channel) =>
sourceId = channel.sourceId
if sourceId of channels and channels[sourceId] is null
channels[sourceId] = channel
channelsLeft -= 1
if channelsLeft is 0
@removeListener 'channel', onChannel
@removeListener 'error', onError
@stop()
resolve channels
else
channel.close()
onError = (error) =>
@removeListener 'channel', onChannel
@removeListener 'error', onError
@stop()
reject error
@addListener 'channel', onChannel
@addListener 'error', onError
@start()
# Called when the Bluetooth LE's powered on state changes.
#
# @param {String} newState the new Bluetooth LE power state
_onBleStateChange: (newState) ->
@_powerState = newState
if newState is 'poweredOn' and @_started is true and
@_bleScanStarted is false
@_bleScanStarted = true
noble.startScanning [], true, (error) =>
if error
@emit 'error', error
# Called when a Bluetooth LE peripheral is found.
#
# @param {Peripheral} peripheral the bluetooth LE peripheral that was
# discovered
_onBleDiscover: (peripheral) ->
return unless peripheral.connectable
uuid = peripheral.id
return if uuid of @_peripherals
@_peripherals[uuid] = true
BleChannel.fromPeripheral(peripheral)
.then (bleChannel) =>
return if bleChannel is null
if @_started is true
@emit 'channel', bleChannel
else
bleChannel.close()
.catch (error) =>
@emit 'error', error
# Called when serialport returns a list of ports.
#
# @param {Error} error
# @param {Array<String>} ports list of ports
_onSerialPortList: (error, ports) ->
if error
@emit 'error', error
return unless ports and @_started is true
for port in ports
continue unless rfconnPath = port.comName
@_onSerialPort rfconnPath
# Called when a serial port is discovered.
#
# @param {String} rfconnPath the path to the serial port
_onSerialPort: (rfconnPath) ->
return if rfconnPath of @_serialPorts
@_serialPorts[rfconnPath] = true
@emit 'channel', new SerialChannel(rfconnPath)
module.exports = new DiscoveryClass