├── .gitignore ├── LICENSE.md ├── README.md ├── display ├── gauge.py └── px4flow_display.py ├── px4flow ├── __init__.py └── mavlink_parser.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyPX4Flow 2 | ========= 3 | 4 | A simple Python package for reading from the PX4Flow optical-flow sensor 5 | 6 |

7 | 8 | I needed an easy way to access data from the PX4Flow sensor using 9 | Python, so I wrote this little package, which 10 | you can 11 | download from github. 12 | It runs on Windows, Linux, and 13 | OS X in Python 2 and 3. As with the other Python packages I've 14 | written, the API is very simple. For example, this program prints the computed X and Y flow velocities from the sensor: 15 | 16 | 17 |

 18 | from px4flow import PX4Flow
 19 | 
 20 | class ShowFlow(PX4Flow):
 21 | 
 22 |     def update(self):
 23 | 
 24 |             print(self.getFlowComp())
 25 | 
 26 | 
 27 | if __name__ == '__main__':
 28 | 
 29 |     sensor = ShowFlow('/dev/ttyACM0')
 30 | 
 31 |     while True:
 32 | 
 33 |         sensor.refresh()
 34 | 
 35 | 
36 | 37 | 38 | As this example shows, PX4Flow is an abstract class that you subclass with a class implementing the 39 | update method. To see the other methods avaiable, look at the 40 | documentation. 41 | 42 |

43 | 44 | I've also included a program that will display the sensor data in a useful way (depicted above) and log the data to 45 | a .CSV file for Excel. 46 | 47 |

48 | 49 |

Instructions

50 | 51 | You will need: 52 | 53 |
    54 |
  1. A PX4Flow kit. I recommend 55 | following the Image Quality and Output 56 | instructions to make sure the sensor is working properly. 57 |
  2. The ability to program in Python 58 |

  3. The PySerial package installed on your computer. 59 | Like many people, I found it easiest to install from source. 60 |

  4. Administrator (root) privileges on your computer 61 |

  5. The PyPX4Flow repository. 62 |
63 | 64 | Once you've downloaded the repositry, use a terminal (Linux or OS X) or command shell 65 | (Windows) to change to the directory where you put it, and issue the command 66 | 67 | 68 |
 69 |   python setup.py install
 70 | 
71 |
72 | 73 | On Linux or OS X, you may need to issue this command as root: 74 | 75 | 76 |
 77 |   sudo python setup.py install
 78 | 
79 |
80 | 81 | Then you should be able to run the px4flow_display.py program in the display folder. 82 | 83 |

84 | 85 |

Known issues

86 | 87 |
    88 | 89 |
  1. Some users have reported a bug in which the display program crashes immediately with a stack trace ending in the following output: 90 | 91 |
     92 |  File "/usr/local/lib/python2.7/dist-packages/px4flow/mavlink_parser.py", line 99, in unpack
     93 |     return struct.unpack(fmt, self.msg[lo:hi])[0:n]
     94 | error: unpack requires a string argument of length 4
     95 | 
    96 | 97 | So far this bug seem to occur only with versions of Python below 2.7.5, so I've added a 98 | branch to the repository that 99 | should work with such earlier versions. If you are running 2.7.5 or above and still experience this problem, 100 | please let me know. 101 | 102 |

  2. At startup, the display program occasionally computes and displays bogus large values for the distance traveled. 103 | This may have to do with the fact that I am not using the checksum to validate each message. 104 | 105 |
106 | 107 |

Copyright and licensing

108 | 109 | Copyright and licensing information (Gnu 110 | LGPL) 111 | can be found in the header of each source file. 112 | 113 |

Acknowledgments

114 | 115 | This work was supported in part by a Commonwealth Research Commercialization Fund 116 | grant from the Center for Innovative Technology (CRCF #MF14F-011-MS) and a 117 | Lenfest summer research grant from Washington and Lee University. 118 | 119 | 120 | -------------------------------------------------------------------------------- /display/gauge.py: -------------------------------------------------------------------------------- 1 | ''' 2 | gauge.py - Gauge-display classes for Tkinter projects 3 | 4 | Copyright (C) 2014 Simon D. Levy 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Lesser General Public License as 8 | published by the Free Software Foundation, either version 3 of the 9 | License, or (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public License 17 | along with this code. If not, see . 18 | ''' 19 | 20 | from sys import version 21 | 22 | if version[0] == '3': 23 | import tkinter as tk 24 | else: 25 | import Tkinter as tk 26 | 27 | class VerticalGauge(object): 28 | ''' 29 | A class for vertical gauges. 30 | ''' 31 | 32 | def __init__(self, canvas, left, bottom, width, height, color, label, minval, maxval, fmt): 33 | ''' 34 | Creates a labeled vertical gauge of specified dimensions on a specified canvas. 35 | FMT for min and max values is as for print(); for example '+%3.3f', '%d', etc. 36 | ''' 37 | 38 | right = left + width 39 | 40 | self.height = height 41 | self.canvas = canvas 42 | self.minval = minval 43 | self.maxval = maxval 44 | 45 | top = bottom - self.height 46 | bbox = (left, bottom, right, top) 47 | self.bbox = bbox 48 | self.rect = canvas.create_rectangle(bbox, fill=color) 49 | canvas.create_rectangle((bbox[0]-1, bbox[1]-1, bbox[2]+1, bbox[3]+1), outline='white') 50 | self._create_label(left, bottom+20, text=label) 51 | self._create_label(left-30, bottom, text= fmt % minval) 52 | self._create_label(left-30, top , text= fmt % maxval) 53 | 54 | def update(self, newval): 55 | ''' 56 | Updates the VerticalGauge with a new value. 57 | ''' 58 | 59 | new_height = self.height * (newval-self.minval) / (self.maxval - self.minval) 60 | bbox = self.bbox 61 | self.canvas.coords(self.rect, (bbox[0], bbox[1], bbox[2], bbox[1]-new_height)) 62 | 63 | def _create_label(self, x, y, text): 64 | 65 | return self.canvas.create_text(x, y, anchor=tk.W, font=('Helvetica', 12), fill='white', text=text) 66 | -------------------------------------------------------------------------------- /display/px4flow_display.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | ''' 4 | px4flow_display.py - Display / logging program for PyPX4Flow package 5 | 6 | Copyright (C) 2014 Simon D. Levy 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU Lesser General Public License as 10 | published by the Free Software Foundation, either version 3 of the 11 | License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU Lesser General Public License 19 | along with this code. If not, see . 20 | ''' 21 | 22 | # Pick your port 23 | PORT = '/dev/ttyACM0' # Linux 24 | #PORT = 'COM5' # Windows 25 | #PORT = '/dev/tty.usbmodem1431' # Mac OS X 26 | 27 | # Sensor params (units are meters, seconds) 28 | 29 | GRND_DIST_MIN = 0.3 30 | GRND_DIST_MAX = 5.0 31 | 32 | VELOCITY_MAX = 5.0 33 | 34 | DISTANCE_MAX = 10 35 | 36 | # Display params (Width, Height, X, Y, Radius, Distance) 37 | 38 | DISPLAY_W = 1000 39 | DISPLAY_H = 300 40 | 41 | GAUGE_Y = DISPLAY_H - 60 42 | GAUGE_W = 90 43 | GAUGE_H = 200 44 | 45 | VELOCITY_X = 450 46 | VELOCITY_Y = DISPLAY_H / 2 47 | VELOCITY_R = 5 48 | VELOCITY_D = 100 49 | 50 | DISTANCE_X = 800 51 | DISTANCE_Y = VELOCITY_Y 52 | DISTANCE_D = VELOCITY_D 53 | DISTANCE_R = 5 54 | 55 | from px4flow import PX4Flow 56 | from gauge import VerticalGauge 57 | 58 | from sys import exit, version 59 | from time import time, strftime 60 | 61 | if version[0] == '3': 62 | import tkinter as tk 63 | else: 64 | import Tkinter as tk 65 | 66 | class PX4FlowPlotter: 67 | 68 | def __init__(self, reader): 69 | 70 | # Store the PX4Flow reader 71 | self.reader = reader 72 | 73 | # Set up the frame 74 | self.root = tk.Tk() 75 | self.frame = tk.Frame(self.root, borderwidth=4, width=DISPLAY_W, height=DISPLAY_H, relief='sunken') 76 | self.frame.master.title('PX4Flow: Hit ESC to quit') 77 | 78 | # Add a canvas for drawing 79 | self.canvas = tk.Canvas(self.frame, width = DISPLAY_W, height = DISPLAY_H, background = 'black') 80 | 81 | # Add a text item for reporting acquisition rate 82 | self.rate_label = self.create_label(DISPLAY_W-140, DISPLAY_H-20, color='red') 83 | 84 | # Add a gauge for grnd_dist 85 | self.grnd_dist_gauge = VerticalGauge(self.canvas, 40, GAUGE_Y, GAUGE_W, GAUGE_H, 'blue', 'Grnd dist (m)', GRND_DIST_MIN, GRND_DIST_MAX, '%2.1f') 86 | 87 | # Add a gauge for quality 88 | self.quality_gauge = VerticalGauge(self.canvas, 200, GAUGE_Y, GAUGE_W, GAUGE_H, 'yellow', ' Quality', 0, 255, '%d') 89 | 90 | # Add axes for velocity 91 | self.create_axis_line(-1, 0) 92 | self.create_axis_line(+1, 0) 93 | self.create_axis_line(0, -1) 94 | self.create_axis_line(0, +1) 95 | 96 | # Add labels for velocity 97 | self.create_label(VELOCITY_X+120, VELOCITY_Y, 'X m/sec') 98 | self.create_label(VELOCITY_X-30, DISPLAY_H-275, 'Y m/sec') 99 | 100 | # Add lines for displaying velocities 101 | self.x_line = self.create_null_line() 102 | self.y_line = self.create_null_line() 103 | 104 | # Add widgets for displaying distance traveled 105 | box_lx = DISTANCE_X - DISTANCE_D 106 | box_rx = DISTANCE_X + DISTANCE_D 107 | box_uy = DISTANCE_Y - DISTANCE_D 108 | box_ly = DISTANCE_Y + DISTANCE_D 109 | self.canvas.create_rectangle((box_lx, box_uy, box_rx, box_ly), outline='white') 110 | self.create_label(box_lx+DISTANCE_D/2+30, box_ly+20, 'X (m)') 111 | self.create_label(box_rx+10, DISTANCE_Y, 'Y (m)') 112 | self.create_label(box_rx+10, box_uy-10, '+%d' % DISTANCE_MAX) 113 | self.create_label(box_lx-30, box_ly+10, '-%d' % DISTANCE_MAX) 114 | self.location_circle = self.canvas.create_oval(self.location_to_oval(0, 0), width=1, outline='red') 115 | self.location_pix_prev = None 116 | 117 | # Pack the widgets into the canvas 118 | self.canvas.pack(fill=tk.BOTH, expand=1) 119 | 120 | # Set up a key event for exit on ESC 121 | self.frame.bind("", self.key) 122 | self.frame.pack() 123 | 124 | # This call gives the frame focus so that it receives input 125 | self.frame.focus_set() 126 | 127 | self.failcount = 0 128 | 129 | def location_to_oval(self, xpix, ypix): 130 | 131 | return xpix-DISTANCE_R, ypix-DISTANCE_R, xpix+DISTANCE_R, ypix+DISTANCE_R, 132 | 133 | def location_to_pixels(self): 134 | 135 | xpix = DISTANCE_X + (self.reader.X_accum/ DISTANCE_MAX) * DISTANCE_D 136 | ypix = DISTANCE_Y + (self.reader.Y_accum/ DISTANCE_MAX) * DISTANCE_D 137 | 138 | return xpix, ypix 139 | 140 | def create_null_line(self): 141 | 142 | return self.canvas.create_line(VELOCITY_X, VELOCITY_Y, VELOCITY_X, VELOCITY_Y, width=4) 143 | 144 | def create_axis_line(self, dx, dy): 145 | 146 | self.canvas.create_line(VELOCITY_X, VELOCITY_Y, VELOCITY_X+dx*VELOCITY_D, VELOCITY_Y+dy*VELOCITY_D, fill='white') 147 | 148 | def create_label(self, x, y, text=None, color='white'): 149 | 150 | return self.canvas.create_text(x, y, anchor=tk.W, font=('Helvetica', 12), fill=color, text=text) 151 | 152 | def run(self): 153 | 154 | # Start timing 155 | self.start_sec = time() 156 | 157 | # Start the recursive timer-task 158 | self.task() 159 | 160 | # Set Tkinter's main loop 161 | self.root.mainloop() 162 | 163 | def task(self): 164 | 165 | reader = self.reader 166 | 167 | # Refresh PX4Flow data 168 | reader.refresh() 169 | 170 | # Ground distance will be None on sensor fail 171 | grnd_dist = reader.H 172 | 173 | # Update sensor display 174 | if grnd_dist: 175 | 176 | xpix, ypix = self.location_to_pixels() 177 | self.canvas.coords(self.location_circle, self.location_to_oval(xpix, ypix)) 178 | if self.location_pix_prev: 179 | self.canvas.create_line(self.location_pix_prev[0], self.location_pix_prev[1], xpix, ypix, fill='red') 180 | self.location_pix_prev = xpix, ypix 181 | 182 | self.grnd_dist_gauge.update(grnd_dist) 183 | self.quality_gauge.update(reader.Quality) 184 | 185 | x = VELOCITY_X + 2 186 | y = VELOCITY_Y + 2 187 | 188 | self.canvas.coords(self.x_line, (x, y, int(VELOCITY_X+reader.X/VELOCITY_MAX*VELOCITY_D), y)) 189 | self.canvas.itemconfigure(self.x_line, fill = 'red' if reader.X < 0 else 'green') 190 | 191 | self.canvas.coords(self.y_line, (x, y, x, int(VELOCITY_Y+reader.Y/VELOCITY_MAX*VELOCITY_D))) 192 | self.canvas.itemconfigure(self.y_line, fill = 'red' if reader.Y < 0 else 'green') 193 | 194 | else: 195 | self.failcount += 1 196 | print('Fail %d' % self.failcount) 197 | 198 | # Report speed after several seconds 199 | elapsed_sec = time() - self.start_sec 200 | if elapsed_sec > 5: 201 | self.canvas.itemconfigure(self.rate_label, text='%d updates / sec' % \ 202 | int(reader.count/elapsed_sec)) 203 | 204 | # Reschedule this task immediately 205 | self.frame.after(1, self.task) 206 | 207 | def click(self, event): 208 | print("Clicked at: ", event.x, event.y) 209 | 210 | def key(self, event): 211 | 212 | # Make sure the frame is receiving input! 213 | self.frame.focus_force() 214 | if event.keysym == 'Escape': 215 | exit(0) 216 | 217 | class PX4FlowReader(PX4Flow): 218 | 219 | def __init__(self, port): 220 | 221 | PX4Flow.__init__(self, port) 222 | 223 | # No readings yet 224 | self.SensorX,self.SensorY = None, None 225 | self.X,self.Y = None, None 226 | self.H = None 227 | self.Quality = None 228 | 229 | # Create logfile named by current date / time 230 | filename = 'px4flow_' + strftime('%d_%b_%Y_%H_%M_%S') + '.csv' 231 | self.logfile = open(filename, 'w') 232 | self.write_and_flush('Time (sec), Ground Dist (m), Flow X, Flow Y, Flow Comp X (m), Flow Comp Y (m), Quality (/255),,') 233 | self.write_and_flush('X accum (m), Y accum (m)\n') 234 | 235 | # These will get the accumulated X,Y distances in meters 236 | self.timeSecPrev = None 237 | self.X_accum = 0 238 | self.Y_accum = 0 239 | 240 | self.count = 0 241 | 242 | # Implements missing method in parent class 243 | def update(self): 244 | 245 | # Grab raw sensor X,Y 246 | self.SensorX,self.SensorY = self.getFlow() 247 | 248 | # Grab computed X,Y in meters 249 | self.X,self.Y = self.getFlowComp() 250 | 251 | # Grab ground distance (height) in meters 252 | self.H = self.getGroundDistance() 253 | 254 | # Grab quality in percent 255 | self.Quality = self.getQuality() 256 | 257 | # Time in seconds 258 | timeSec = self.getTime() / 1e6 259 | 260 | # Computed flow in meters per second 261 | flowCompX, flowCompY = self.getFlowComp() 262 | 263 | self.write_and_flush('%6.3f, %+3.3f, %d, %d, %+3.3f, %+3.3f, %d' % \ 264 | (timeSec, self.H, self.SensorX, self.SensorY, self.X, self.Y, self.Quality)) 265 | 266 | # After first iteration, compute accumualted distances 267 | if self.count: 268 | 269 | # Compute distance if elapsed time available 270 | if self.timeSecPrev: 271 | 272 | elapsedSec = timeSec - self.timeSecPrev 273 | 274 | # Elapsed time should never be more than a small fraction of a second 275 | if elapsedSec < 0.1: 276 | self.X_accum += flowCompX * elapsedSec 277 | self.Y_accum += flowCompY * elapsedSec 278 | 279 | self.timeSecPrev = timeSec 280 | 281 | self.write_and_flush(',,%+3.3f, %+3.3f' % (self.X_accum, self.Y_accum)) 282 | 283 | self.write_and_flush('\n') 284 | 285 | # Update count for speed reporting 286 | self.count += 1 287 | self.timeSecPrev = timeSec 288 | 289 | def write_and_flush(self, s): 290 | 291 | self.logfile.write(s) 292 | self.logfile.flush() 293 | 294 | if __name__ == "__main__": 295 | 296 | reader = PX4FlowReader(PORT) 297 | 298 | plotter = PX4FlowPlotter(reader) 299 | 300 | plotter.run() 301 | -------------------------------------------------------------------------------- /px4flow/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | PX4Flow - a Python class for collecting data from the PX4Flow sensor 3 | 4 | Copyright (C) 2014 Simon D. Levy 5 | 6 | This code is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Lesser General Public License as 8 | published by the Free Software Foundation, either version 3 of the 9 | License, or (at your option) any later version. 10 | 11 | This code is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public License 17 | along with this code. If not, see . 18 | ''' 19 | 20 | # Message ID fom https://pixhawk.ethz.ch/mavlink/ 21 | MSG_OPTICAL_FLOW = 100 22 | 23 | # Arbitrary 24 | _BUFSIZE = 2048 25 | 26 | import serial 27 | import platform 28 | 29 | from px4flow.mavlink_parser import MAVLinkParser 30 | 31 | class PX4Flow(MAVLinkParser): 32 | ''' 33 | An abstract class for reading from the PX4Flow optical flow sensor. Your subclass should provide an 34 | update(self) method that calls one or more of the accessor methods below. 35 | ''' 36 | 37 | def __init__(self, port): 38 | ''' 39 | Creates a new PX4Flow object on the specified port. 40 | ''' 41 | 42 | # Baud rate is unspecified 43 | self.dev = serial.Serial(port) 44 | 45 | # Create MAVLink object for parsing 46 | MAVLinkParser.__init__(self, self, MSG_OPTICAL_FLOW) 47 | 48 | # Require refresh before first reading 49 | self._refreshed = False 50 | 51 | def close(self): 52 | ''' 53 | Closes the port on which the sensor was opened. 54 | ''' 55 | self.dev.close() 56 | 57 | def refresh(self): 58 | ''' 59 | Refreshes the optical flow reading. 60 | ''' 61 | 62 | self._refreshed = True 63 | 64 | # Grab bytes from the device 65 | bytes = self.dev.read(_BUFSIZE) 66 | 67 | # Check for MAVLINK messages in bytes 68 | MAVLinkParser.process(self, bytes) 69 | 70 | def getFlow(self): 71 | ''' 72 | Returns raw sensor X,Y. 73 | ''' 74 | 75 | self._check_refreshed() 76 | 77 | return MAVLinkParser.unpack(self, 'hh', 20, 24, 2) 78 | 79 | def getFlowComp(self): 80 | ''' 81 | Returns computed X,Y in meters. 82 | ''' 83 | 84 | self._check_refreshed() 85 | 86 | return MAVLinkParser.unpack(self, 'ff', 8, 16, 2) 87 | 88 | def getGroundDistance(self): 89 | ''' 90 | Returns ground distance (height) in meters. 91 | ''' 92 | 93 | self._check_refreshed() 94 | 95 | return MAVLinkParser.unpack1(self, 'f', 16, 20) 96 | 97 | def getQuality(self): 98 | ''' 99 | Returns quality in percent. 100 | ''' 101 | 102 | self._check_refreshed() 103 | 104 | return MAVLinkParser.unpack_uint8(self, 25) 105 | 106 | def getTime(self): 107 | ''' 108 | Returns current time in microseconds. 109 | ''' 110 | 111 | self._check_refreshed() 112 | 113 | return MAVLinkParser.unpack1(self, 'Q', 0, 8) 114 | 115 | def _check_refreshed(self): 116 | 117 | if not self._refreshed: 118 | 119 | raise Exception('Attempt to read without refresh()') 120 | -------------------------------------------------------------------------------- /px4flow/mavlink_parser.py: -------------------------------------------------------------------------------- 1 | ''' 2 | mavlink_parser.py - Simon's homebrew code for parsing MAVLink messages. 3 | DOES NOT VERIFY CHECKSUM!!! 4 | 5 | Based on http://en.wikipedia.org/wiki/MAVLink, but hoping to replace it 6 | with pymavlink, so we can get checksum and other features. 7 | 8 | Copyright (C) 2014 Simon D. Levy 9 | 10 | This code is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU Lesser General Public License as 12 | published by the Free Software Foundation, either version 3 of the 13 | License, or (at your option) any later version. 14 | 15 | This code is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU Lesser General Public License 21 | along with this code. If not, see . 22 | ''' 23 | 24 | import struct 25 | 26 | # States for message parsing 27 | # From http://qgroundcontrol.org/mavlink/start#packet_anatomy 28 | STATE_DFLT = 0 29 | STATE_STX = 1 30 | STATE_LEN = 2 31 | STATE_SEQ = 3 32 | STATE_SYS = 4 33 | STATE_COMP = 5 34 | STATE_MSG = 6 35 | STATE_CKA = 7 36 | STATE_CKB = 8 37 | 38 | # STX delimiter byte for Mavlink 1.0 39 | STX_BYTE = 0XFE 40 | 41 | class MAVLinkParser(object): 42 | 43 | def process(self, buf): 44 | 45 | for b in bytearray(buf): 46 | 47 | if b == STX_BYTE: 48 | self.state = STATE_STX 49 | 50 | elif self.state == STATE_STX: 51 | self.msglen = b 52 | self.state = STATE_LEN 53 | 54 | elif self.state == STATE_LEN: 55 | self.state = STATE_SEQ 56 | 57 | elif self.state == STATE_SEQ: 58 | self.state = STATE_SYS 59 | 60 | elif self.state == STATE_SYS: 61 | self.state = STATE_COMP 62 | 63 | elif self.state == STATE_COMP: 64 | self.msgid = b 65 | self.msg = bytearray('', 'utf8') 66 | self.state = STATE_MSG 67 | 68 | elif self.state == STATE_MSG: 69 | self.msg.append(b) 70 | if len(self.msg) == self.msglen: 71 | self.state = STATE_CKA 72 | 73 | elif self.state == STATE_CKA: 74 | self.state = STATE_CKB 75 | 76 | elif self.state == STATE_CKB: 77 | if self.msgid == self.tgtid: 78 | self.handler.update() 79 | self.msg = bytearray('', 'utf8') 80 | self.state = STATE_DFLT 81 | 82 | def __init__(self, handler, targetid): 83 | 84 | self.state = STATE_DFLT 85 | self.msglen = 0 86 | self.msgid = 0 87 | self.msg = bytearray('', 'utf8') 88 | 89 | self.handler = handler 90 | self.tgtid = targetid 91 | 92 | def unpack_uint8(self, lo): 93 | return self.unpack1('B', lo, lo+1) 94 | 95 | def unpack1(self, fmt, lo, hi): 96 | return self.unpack(fmt, lo, hi, 1)[0] 97 | 98 | def unpack(self, fmt, lo, hi, n): 99 | return struct.unpack(fmt, self.msg[lo:hi])[0:n] 100 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | setup.py - Python distutils setup file for PyPX4Flow package. 5 | 6 | Copyright (C) 2014 Simon D. Levy 7 | 8 | This code is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU Lesser General Public License as 10 | published by the Free Software Foundation, either version 3 of the 11 | License, or (at your option) any later version. 12 | 13 | This code is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU Lesser General Public License 19 | along with this code. If not, see . 20 | ''' 21 | 22 | from distutils.core import setup 23 | 24 | setup (name = 'PyPX4Flow', 25 | version = '0.1', 26 | description = 'PX4Flow sensor utility', 27 | packages = ['px4flow'], 28 | author='Simon D. Levy', 29 | author_email='simon.d.levy@gmail.com', 30 | url='http://home.wlu.edu/~levys/software/pypx4flow', 31 | license='LGPL', 32 | platforms='Linux; Windows; OS X' 33 | ) 34 | --------------------------------------------------------------------------------