-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsrf01.py
437 lines (346 loc) · 15.3 KB
/
srf01.py
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
################################################################################
# srf01
#
# Created: 2016-11-08 14:46:49.825158
# By: Andrea Bau'
################################################################################
"""
.. module:: srf01
****************
srf01 Library
****************
This module contains class definitions and methods to access most of the functionality of srf01 Ultrasonic range finder.
Sensor's charactheristics and detailed descriptions can be found at https://www.robot-electronics.co.uk/htm/srf01tech.htm.
These sensors use one pin serial interfaces, this library however rely on Zerynth's *streams* class and both pin of a serial peripheral are needed.
Depending on the board that is used is necessary to put a resistor (1-5 kOhm) between Tx and Rx board's pins and then connect the sensor's serial port to Rx pin.
With a single serial peripheral it is possible to control up to 16 SRF01's.
Example::
import streams
from community.anba.srf01 import srf01
streams.serial()
my_sensors = srf01.Srf(D0,SERIAL1)
print("cheking versions...")
sft01 = my_sensors.get_software_version(1) # retrieve software version of sensor with address 1
sft04 = my_sensors.get_software_version(4) # retrieve software version of sensor with address 4
if sft01!=2 or sft04!=2:
print("wrong sensor's software version")
else:
print("done")
while True:
my_sensors.do_range() # all connected sensor starts the ranging routine at the same time, the returned distance will be in cm
sleep(70)
rng01 = my_sensors.get_last_range(1)
rng04 = my_sensors.get_last_range(4)
print("-----------------")
print("sensor 1: %03d cm"%rng01)
print("sensor 4: %03d cm"%rng04)
sleep(1000)
"""
import streams
RANGE_IN = 0x50 #0-addressable
RANGE_CM = 0x51 #0-addressable
RANGE_IN_R = 0x53 #return 2 bytes after ~70ms
RANGE_CM_R = 0x54 #return 2 bytes after ~70ms
FAKE_RANGE_IN = 0x56 #0-addressable
FAKE_RANGE_CM = 0x57 #0-addressable
FAKE_RANGE_IN_R = 0x59 #return 2 bytes after ~70ms
FAKE_RANGE_CM_R = 0x5A #return 2 bytes after ~70ms
BURST = 0x5C #0-addressable
SOFTWARE_VERSION = 0x5D #return 1 byte
RANGE = 0x5E #return 2 bytes
STATUS = 0x5F #return 1 byte
SLEEP = 0x60 #0-addressable
UNLOCK = 0x61 #0-addressable
SET_ADV_MODE = 0x62 #0-addressable
CLR_ADV_MODE = 0x63 #0-addressable
BAUD19200 = 0x64 #0-addressable
BAUD38400 = 0x65 #0-addressable
WAKEUP = 0xFF #0-addressable
CHANGE01 = 0XA0
CHANGE02 = 0XAA
CHANGE03 = 0XA5
READ_ATTEMPT = 10
new_exception(InvalidAddress,ValueError,"Invalid address value! Use an integer between 1 and 16")
new_exception(InvalidUnit,ValueError,"Invalid range unit! Use \"cm\" or \"in\"")
new_exception(InvalidAddress0,ValueError,"Invalid address! Use an integer between 1 and 16 or 0")
streams.serial()
class Srf():
"""
==================
Srf Class
==================
.. class:: Srf(pin,driver)
Creates a Srf instance through which is possible to interact with srf01 sensors.
Communications with sensors is via *driver* serial port, where *pin* is the Tx pin.
"""
def __init__(self,pin,driver):
self.pin = pin
self.driver = driver
def change_address(self,current_addr,new_addr):
"""
.. method:: change_address(current_addr,new_addr)
With this method it is possible to change the sensor address. Only one sensor have to be
connected to the serial in order to use this method. Valid address values are integeres
from ``1`` to ``16`` included, otherwise :exc:`InvalidAddress` exception is raised.
::
import streams
from community.anba.srf01 import srf01
streams.serial()
my_sensors = srf01.Srf(D0,SERIAL1)
try:
my_sensors.change_address(1,5)
except Exception as e:
print(e)
"""
if not _is_valid_addr(current_addr) or not _is_valid_addr(new_addr):
raise InvalidAddress
self._writeNread(current_addr,CHANGE01)
self._writeNread(current_addr,CHANGE02)
self._writeNread(current_addr,CHANGE03)
self._writeNread(current_addr,new_addr)
def get_software_version(self,addr):
"""
.. method:: get_software_version(addr)
Return a small integer representing the software version loaded in the sensor with address
*addr*. If serial communication fails ``-1`` is returned. If *addr* is not an integer between ``1`` and
``16`` included, :exc:`InvalidAddress` exception is raised.
::
import streams
from community.anba.srf01 import srf01
streams.serial()
my_sensors = srf01.Srf(D0,SERIAL1)
print("Checking software version...",end="")
for i in range(10):
print(".",end="")
sft13 = my_sensors.get_software_version(13)
if sft13 != -1:
print(" done!")
print(Sensor 13's software version: ",sft13)
break
if sft13 == -1:
print(" ooops, failed :( ")
"""
if not _is_valid_addr(addr):
raise InvalidAddress
sftVer = self._writeNread(addr,SOFTWARE_VERSION,nbit=1)
return sftVer[0]
def get_status(self,addr):
"""
.. method:: get_status(addr)
Return a small integer representing the status (see sensor's technical documentation for details about status value) of the sensor with address
*addr*. If serial communication fails return ``-1``. If *addr* is not an integer between ``1`` and
``16`` included, :exc:`InvalidAddress` exception is raised. If the sensor is in advanced mode and it is locked, it can measure range all the way
down to zero. Otherwise if the sensor is not locked or it is in standard mode the minimi range the SRF01 sensor ca detect is around 18 cm or 7 inches.
::
import streams
from community.anba.srf01 import srf01
streams.serial()
my_sensors = srf01.Srf(D0,SERIAL1)
print("Checking sensor's status...",end="")
for i in range(10):
print(".",end="")
sft08 = my_sensors.get_status(8)
if sft08 != -1:
print(" done!")
break
if sft08 == -1:
print(" ooops, failed :( ")
else:
if sft08 == 0:
print("Standard mode, unlocked")
elif sft08 == 1:
print("Standard mode, locked")
elif sft08 == 2:
print("Advanced mode, unlocked")
else: #stf08 == 3
print("Advanced mode, locked")
"""
if not _is_valid_addr(addr):
raise InvalidAddress
status = self._writeNread(addr,STATUS,nbit=1)
return status[0]
def get_last_range(self,addr):
"""
.. method:: get_last_range(addr)
Return a small integer representing last range done by the sensor with address *addr*.
The unit of measure (cm or in) of the returned value depends on the last range command received by that sensor.
If serial communication fails this method return ``-1``. If *addr* is not an integer between ``1`` and
``16`` included, :exc:`InvalidAddress` exception is raised.
::
import streams
importfrom community.anba.srf01 import srf01
streams.serial()
my_sensors = srf01.Srf(D0,SERIAL1)
my_sensors.do_range(3)
my_sensors.do_range(7,"in")
sleep(70)
rng03 = my_sensors.get_last_range(3)
rng07 = my_sensors.get_last_range(7)
print("Sensor 3: %03d cm"%rng03)
print("Sensor 7: %03d in"%rng07)
"""
if not _is_valid_addr(addr):
raise InvalidAddress
resp = self._writeNread(addr,RANGE,nbit=2)
if len(resp)==2:
rangeInt = 256*resp[0]+resp[1]
return rangeInt
else:
return resp[0]
def do_range(self,addr=0,unit="cm"):
"""
.. method:: do_range(addr=0,unit="cm")
This method initiate a ranging on srf01 sensor. Using ``get_last_range()`` method after ~70 ms it
is possible to get the result of the ranging. If *addr* is equal to ``0`` (default value) all
connected sensors will start the ranging at the same time, otherwise only the sensor with address
equal to *addr* will. If *addr* is not any of these value :exc:`InvalidAddress0` exception is raised.
*unit* define the measure unit of the result (retrievable with ``get_last_range()``), it can be ``"cm"``
or ``"in"`` otherwise :exc:`InvalidUnit` exception is raised.
"""
if addr !=0 and not _is_valid_addr(addr):
raise InvalidAddress0
if unit not in ["cm","in"]:
raise InvalidUnit
elif unit == "in":
cmd = RANGE_IN
else:
cmd = RANGE_CM
self._writeNread(addr,cmd)
def burst(self,addr=0):
"""
.. method:: burst(addr=0)
Transmit a burst without doing the ranging. If *addr* is equal to ``0`` (default value) all
connected sensors will send the burst at the same time, otherwise only the sensor with address
equal to *addr* will. If *addr* is not any of these value :exc:`InvalidAddress0` exception is raised.
"""
if addr!=0 and not _is_valid_addr(addr):
raise InvalidAddress0
self._writeNread(addr,BURST)
def do_fake_range(self,addr=0,unit="cm"):
"""
.. method:: do_fake_range(addr=0,unit="cm")
Same as ``do_range()`` except that the sensor do not send the 8-cycle ultrasonic burst out.
This method is used where the burst has been transmitted by another sonar.
"""
if addr !=0 and not _is_valid_addr(addr):
raise InvalidAddress0
if unit not in ["cm","in"]:
raise InvalidUnit
elif unit == "in":
cmd = FAKE_RANGE_IN
else:
cmd = FAKE_RANGE_CM
self._writeNread(addr,cmd)
def get_range(self,addr,unit="cm"):
"""
.. method:: get_range(addr,unit="cm")
Same as ``do_range()`` but this method block the thread (for ~70 ms) until the measured range is returned or ``-1`` if serial communication fails.
Unlike ``do_range()`` here *addr* can not be ``0`` (to avoid multiple sensors to write on the communication bus at the same time), if *addr* is not an integer between ``1`` and
``16`` included, :exc:`InvalidAddress` exception is raised.
::
import streams
from community.anba.srf01 import srf01
streams.serial()
my_sensors = srf01.Srf(D0,SERIAL1)
while True:
rng04 = my_sensors.get_range(3)
rng09 = my_sensors.get_range(9,"in")
print("------------")
print("Sensor 4: %03d cm"%rng04)
print("Sensor 9: %03d in"%rng09)
sleep(2000)
"""
if not _is_valid_addr(addr):
raise InvalidAddress
if unit not in ["cm","in"]:
raise InvalidUnit
elif unit == "in":
cmd = RANGE_IN_R
else:
cmd = RANGE_CM_R
resp = self._writeNread(addr,cmd,nbit=2,delay=65)
if len(resp)==2:
rangeInt = 256*resp[0]+resp[1]
return rangeInt
else:
return resp[0]
def get_fake_range(self,addr,unit="cm"):
"""
.. method:: get_fake_range(addr,unit="cm")
Same as ``get_range()`` except that the sensor do not send the 8-cycle ultrasonic burst out.
This method is used where the burst has been transmitted by another sonar.
"""
if not _is_valid_addr(addr):
raise InvalidAddress
if unit not in ["cm","in"]:
raise InvalidUnit
elif unit == "in":
cmd = FAKE_RANGE_IN_R
else:
cmd = FAKE_RANGE_CM_R
resp = self._writeNread(addr,cmd,nbit=2,delay=65)
if len(resp)==2:
rangeInt = 256*resp[0]+resp[1]
return rangeInt
else:
return resp[0]
def set_advanced_mode(self,addr=0):
"""
.. method:: set_advanced_mode(addr=0)
This method set the sensor on advanced mode, if *addr* is equal to ``0`` all
connected sensors will be set.
"""
if addr !=0 and not _is_valid_addr(addr):
raise InvalidAddress0
self._writeNread(addr,SET_ADV_MODE)
def clear_advanced_mode(self,addr=0):
"""
.. method:: clear_advanced_mode(addr=0)
This method set the sensor on standard mode, if *addr* is equal to ``0`` all
connected sensors will be set.
"""
if addr !=0 and not _is_valid_addr(addr):
raise InvalidAddress0
self._writeNread(addr,CLR_ADV_MODE)
def _unlock(self,addr=0):
if addr !=0 and not _is_valid_addr(addr):
raise InvalidAddress0
self._writeNread(addr,UNLOCK)
def _writeNread(self,addr,cmd,baud_rate=9600,nbit=0,delay=0):
digitalWrite(self.pin,LOW)
pinMode(self.pin,OUTPUT)
sleep(4)
channel = streams.serial(self.driver,baud_rate,set_default=False)
channel.write(chr(addr))
channel.write(chr(cmd))
thr = []
for i in range(READ_ATTEMPT):
if channel.available() >= 2:
thr = channel.read(2)
break
else:
sleep(1)
if len(thr)!=2:
return [-1]
resp = []
if nbit!=0:
if delay!=0:
sleep(delay)
resp = []
for i in range(READ_ATTEMPT):
if channel.available() >=nbit:
resp = channel.read(nbit)
break
else:
sleep(1)
if len(resp)!=nbit:
resp = [-1]
channel.close()
digitalWrite(self.pin,LOW)
pinMode(self.pin,OUTPUT)
return resp
def _is_valid_addr(addr):
if addr>=1 and addr<=16:
return True
else:
return False