discoverpixy
screen_tracking.c
Go to the documentation of this file.
1 /**************************************************************************************************************************************
2 * Project: discoverpixy
3 * Website: https://github.com/t-moe/discoverpixy
4 * Authors: Aaron Schmocker, Timo Lang
5 * Institution: BFH Bern University of Applied Sciences
6 * File: common/app/screen_tracking.c
7 *
8 * Version History:
9 * Date Autor Email SHA Changes
10 * 2015-05-16 timolang@gmail.com e46314b Added Tracking Screen and implemented "Reference Tracking" and "Color Region Selection"
11 * 2015-05-25 timolang@gmail.com 8088014 Updated Tracking Screen so that the implementations are separated into different method groups.
12 * 2015-06-06 aaron@duckpond.ch 8c264c2 Comment refactoring, updated PID values
13 * 2015-06-06 aaron@duckpond.ch a04cda9 Refactured comments and implemented a bugfix for the PID controller
14 * 2015-06-07 aaron@duckpond.ch 802d3df Fixed pid controller and refactored code
15 * 2015-06-07 aaron@duckpond.ch 3d98ca9 Minor changes
16 * 2015-06-07 timolang@gmail.com c87220d Renamed pixy_helper to pixy_frame. Updated docu of appliaction. added doxygen comments to pixy_{frame,control}.h
17 *
18 **************************************************************************************************************************************/
19 
20 #include "screen_tracking.h"
21 #include "pixy_control.h"
22 #include "button.h"
23 #include "checkbox.h"
24 #include "tft.h"
25 #include "touch.h"
26 #include "pixy.h"
27 #include "system.h"
28 #include "pixy_frame.h"
29 
30 static BUTTON_STRUCT b_back; //Button to navigate back
31 static BUTTON_STRUCT b_select; //Button to start the color region selection
32 static CHECKBOX_STRUCT c_frame_toggle; //Checkbox to toggle video data on/off
33 static TOUCH_AREA_STRUCT a_area; //Touch area for the color region selection
34 
35 //Callback for when the user presses the "back" button
36 static void b_back_cb(void* button)
37 {
38  gui_screen_back(); //navigate back to the previous screen
39 }
40 
41 static volatile bool frame_visible = false; //Whether or not the video data should be displayed
42 static void c_frame_toggle_cb(void* checkbox, bool checked)
43 {
44  frame_visible = checked; //Set the visibility of the frame to the checked state of the checkbox
45  //Frame will be drawn in the main loop below
46 }
47 
48 static enum {detecting, init, tracking, preselecting, abortselecting, selecting, selected, error} state; //Current state of the screen state machine
49 
50 static POINT_STRUCT point1; //First point of the rectangle selected by the user (color region selection)
51 static POINT_STRUCT point2; //End point of the rectangle selected by the user (color region selection)
52 static bool point1_valid; //Whether or not we have a valid first point
53 
54 //Callback for when the user presses the "select color" button
55 static void b_select_cb(void* button)
56 {
57  if (state == selecting) { //we're currently selecting a color region
58  state = abortselecting; //Abort selecting!!
59  } else if (state == tracking) { //we're currently watching the tracking
60  state = preselecting; //start selecting
61  }
62 }
63 
64 //Video Region properties
65 //The camera records with 320*200px, but we need to keep a 1px border because of color interpolation (bayer format)
66 #define FRAME_START_X 1 //x-Coordinate of the top-left point of the frame rectangle on display
67 #define FRAME_START_Y 41 //y-Coordinate of the top-left point of the frame rectangle on display
68 #define FRAME_WIDTH 318 //Width of the video frame
69 #define FRAME_HEIGHT 198 //Height of the video frame
70 #define FRAME_END_X FRAME_START_X +FRAME_WIDTH-1 //x-Coordinate of the bottom-right point of the frame rectangle
71 #define FRAME_END_Y FRAME_START_Y +FRAME_HEIGHT-1 //y-Coordinate of the bottom-right point of the frame rectangle
72 
73 //Callback for when the user touches the frame area to select a color region.
74 //Note: It doesn't matter in which direction the user draws the rectangle, we'll normalize the coordinates later
75 static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction)
76 {
78 
79  switch (triggeredAction) {
80  case PEN_DOWN: //The user just put down the pen
81  point1.x = p.x - FRAME_START_X; //Calculate x-Coordinate relative to frame start
82  point1.y = p.y - FRAME_START_Y; //Calculate y-Coordinate relative to frame start
83  point1_valid = true; //The point1 is now valid
84  break;
85 
86  case PEN_UP: //The user took the pen away
87  if (point1_valid) { //only execute if point1 is valid
88  point2.x = p.x - FRAME_START_X; //Calculate x-Coordinate relative to frame start
89  point2.y = p.y - FRAME_START_Y; //Calculate y-Coordinate relative to frame start
90  state = selected;
91  }
92 
93  break;
94  }
95 }
96 
97 //Prototype for tracking start/stop methods
98 typedef void (*TRACKING_VOID_CALLBACK)(void* tracking_config);
99 //Prototype for tracking update method
100 typedef void (*TRACKING_BLOCK_CALLBACK)(void* tracking_config, struct Block* blocks, int num_blocks);
101 
102 //Structure to save callbacks and settings of a tracking implementation
103 typedef struct {
108 
109 //Methods for our tracking implementation ahead
110 static int16_t servo_x = 0;
111 static int16_t servo_y = 0;
112 
113 //Method/Callback to start our tracking
114 void tracking_our_start(void* tracking_config)
115 {
116  //Activate pixy's data send program
117  int32_t response;
118  int return_value;
119 
120  servo_x = servo_y = 500; // set a default value of 500
121  pixy_rcs_set_position(0, servo_x); // set default
122  pixy_rcs_set_position(1, servo_y); // set default
123 
124  return_value = pixy_command("runprog", INT8(0), END_OUT_ARGS, &response, END_IN_ARGS);
125 }
126 
127 //Method/Callback to stop our tracking
128 void tracking_our_stop(void* tracking_config)
129 {
130  //Stop pixy's data send programm
131  int32_t response;
132  int return_value;
133  return_value = pixy_command("stop", END_OUT_ARGS, &response, END_IN_ARGS);
134 }
135 
136 //Method/Callback to calculate one step of our tracking
137 void tracking_our_update(void* tracking_config, struct Block* blocks, int num_blocks)
138 {
139 
140  if (num_blocks <= 0) { // Check if there are blocks available
141  return; // When there are none, do nothing
142  }
143 
144  uint16_t x = blocks[0].x; // Get x coordinate of the biggest object
145  uint16_t y = blocks[0].y; // Get y coordinate of the biggest object
146 
147  int16_t xset = 0;
148  int16_t yset = 0;
149 
150  xset = (servo_x + pixy_PID_X((FRAME_WIDTH / 2), x)); // calculate the PID output for x
151  yset = (servo_y - pixy_PID_Y((FRAME_HEIGHT / 2), y)); // calculate the PID output for y
152 
153  xset = (xset < 0) ? 0 : xset; // x lower boundary check
154  xset = (xset > 1000) ? 1000 : xset; // x upper boundary check
155 
156  yset = (yset < 0) ? 0 : yset; // y lower boundary check
157  yset = (yset > 1000) ? 1000 : yset; // y upper boundary check
158 
159  servo_x = xset; // update the global, static variable for x
160  servo_y = yset; // update the global, statuc variable for y
161 
162  pixy_rcs_set_position(0, servo_x); // set the new x position
163  pixy_rcs_set_position(1, servo_y); // set the new y position
164 }
165 
166 //Variable which stores all the callbacks and settings for our tracking implementation
171 };
172 
173 //Methods for reference tracking implementation ahead
174 
175 //Method/Callback to start reference tracking
176 void tracking_reference_start(void* tracking_config)
177 {
178  //Run reference tracking
179  int32_t response;
180  int return_value;
181  return_value = pixy_command("runprog", INT8(2), END_OUT_ARGS, &response, END_IN_ARGS);
182 }
183 
184 //Method/Callback to stop reference tracking
185 void tracking_reference_stop(void* tracking_config)
186 {
187  //Stop reference tracking
188  int32_t response;
189  int return_value;
190  return_value = pixy_command("stop", END_OUT_ARGS, &response, END_IN_ARGS);
191 }
192 
193 //Method/Callback to calculate one step of the reference tracking
194 void tracking_reference_update(void* tracking_config, struct Block* blocks, int num_blocks)
195 {
196  //Nothing to do here. Pixy does it all.
197 }
198 
199 //Variable which stores all the callbacks and settings for the reference tracking implementation
204 };
205 
206 //Pointer to the currently active tracking implementation. See also tracking_set_mode
208 
209 //Method to set the current tracking implementation. This function is exported and should be called before getting the screen
211 {
212  //Depending on the enum value let tracking_current point to a different setting/callback structure
213  switch (impl) {
214  case OUR_TRACKING:
215  tracking_current = &tracking_our;
216  break;
217 
218  case REFERENCE_TRACKING:
219  tracking_current = &tracking_reference;
220  break;
221 
222  default:
223  tracking_current = NULL;
224  break;
225  }
226 }
227 
228 //Callback for when the screen is entered/loaded
229 static void enter(void* screen)
230 {
231  tft_clear(WHITE);
232 
233  //"Back" button
234  b_back.base.x1 = 5; //Start X of Button
235  b_back.base.y1 = 5; //Start Y of Button
236  b_back.base.x2 = AUTO; //Auto Calculate X2 with String Width
237  b_back.base.y2 = AUTO; //Auto Calculate Y2 with String Height
238  b_back.txtcolor = WHITE; //Set foreground color
239  b_back.bgcolor = HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
240  b_back.font = 0; //Select Font
241  b_back.text = "Back"; //Set Text (For formatted strings take sprintf)
242  b_back.callback = b_back_cb; //Call b_back_cb as Callback
243  gui_button_add(&b_back); //Register Button (and run the callback from now on)
244 
245 
246  //"Select color" button
247  b_select.base.x1 = 150;
248  b_select.base.y1 = 5;
249  b_select.base.x2 = AUTO;
250  b_select.base.y2 = AUTO;
251  b_select.txtcolor = WHITE;
252  b_select.bgcolor = HEX(0xAE1010);
253  b_select.font = 0;
254  b_select.text = "Select Color";
255  b_select.callback = b_select_cb;
256  gui_button_add(&b_select);
257 
258  //"Frame visible" checkbox
259  c_frame_toggle.base.x1 = 50;
260  c_frame_toggle.base.x2 = 50 + 16;
261  c_frame_toggle.base.y1 = 5;
262  c_frame_toggle.base.y2 = 5 + 16;
263  c_frame_toggle.checked = frame_visible;
264  c_frame_toggle.fgcolor = CHECKBOX_WIN_FG_COLOR;
265  c_frame_toggle.callback = c_frame_toggle_cb;
266  gui_checkbox_add(&c_frame_toggle);
267  tft_print_line(73, 8, BLACK, TRANSPARENT, 0, "Show Video");
268 
269 
270  //Area to select a "color region"
271  a_area.hookedActions = PEN_DOWN | PEN_UP;
272  a_area.x1 = FRAME_START_X;
273  a_area.y1 = FRAME_START_Y;
274  a_area.x2 = FRAME_END_X;
275  a_area.y2 = FRAME_END_Y;
276  a_area.callback = touchCB;
277  //Do not register it here, we do that later
278 
279  if (tracking_current == NULL) {
280  state = error;
281  } else {
282  state = detecting; //Start with the detecting state
283  }
284 }
285 
286 //Callback for when the screen is left/unloaded
287 static void leave(void* screen)
288 {
289  //Remove buttons and checkbox
290  gui_button_remove(&b_back);
291  gui_button_remove(&b_select);
292  gui_checkbox_remove(&c_frame_toggle);
293 
294  if (state == selecting) { //the user left the screen in the "selecting" phase
295  touch_unregister_area(&a_area); //remove the touch area
296  }
297 
298  if (state == tracking) { //the user left the screen in the "tracking" phase
299  tracking_current->stop(tracking_current); //stop tracking
300  pixy_led_set_RGB(0, 0, 0);
301  }
302 }
303 
304 //Callback for when the screen should be updated
305 //This is the main loop of the screen. This method will be called repeatedly
306 static void update(void* screen)
307 {
308  switch (state) {
309  case detecting: //Detecting State: Where we try to connect to the pixy
310  if (pixy_init() == 0) { //Pixy connection ok
311  state = init; //Go to next state
312  }
313 
314  break;
315 
316  case init: //Init State: Where we start the tracking
317  tracking_current->start(tracking_current);
318  state = tracking;
319  break;
320 
321  case tracking: //Tracking state: Where we render the frame and the tracked objects
322  pixy_service(); //Receive events (e.g. block-data) from pixy
323 
324  if (pixy_blocks_are_new()) { //There are new blocks available
325  if (frame_visible) { //If the user want's us to draw the video data
327  } else { //the user want's a colored background
329  }
330 
331 #define BLOCK_BUFFER_SIZE 5 //The maximum amount of blocks that we want to receive
332  struct Block blocks[BLOCK_BUFFER_SIZE]; //Storage to receive blocks from pixy
333  int blocks_received = pixy_get_blocks(BLOCK_BUFFER_SIZE, blocks); //Try to receive up to BLOCK_BUFFER_SIZE Blocks from pixy
334 
335  if (blocks_received >= 0) { //block receiving ok
336  tracking_current->update(tracking_current, blocks, blocks_received); //apply tracking
337 
338  //Draw blocks
339  for (int i = 0; i < blocks_received; i++) { //for each received block
340  struct Block* block = &(blocks[i]);
341  //block.x and block.y are the center coordinates of the object relative to the camera origin.
342  uint16_t x = block->x - 1 + FRAME_START_X - block->width / 2; //Calculate x-Coordinate on the display
343  uint16_t y = block->y - 1 + FRAME_START_Y - block->height / 2; //Calculate y-Coordinate on the display
344  tft_draw_rectangle(x, y, x + block->width - 1, y + block->height - 1, WHITE); //Draw a white rectangle
345  }
346  }
347  }
348 
349  break;
350 
351  case preselecting: { //Pre-Selecting State: Where we set up the color region selection
352  tracking_current->stop(tracking_current); //Stop tracking
353 
355 
356  touch_register_area(&a_area); //Register touch area and receive events from now on
357  point1_valid = false; //we start with an invalid point1
358 
359  b_select.text = "Abort"; //Change the button text to "Abort"
360  gui_button_redraw(&b_select); //redraw button
361 
362  state = selecting; //The user can now select a region
363  }
364  break;
365 
366  case selected: { //Selected State: Where we send the users selection to pixy
367  //Ensure that (x1,y1) represent the top-left point and (x2,y2) the bottom-right.
368  unsigned int tmp;
369 
370  if (point1.x > point2.x) {
371  tmp = point1.x;
372  point1.x = point2.x;
373  point2.x = tmp;
374  }
375 
376  if (point1.y > point2.y) {
377  tmp = point1.y;
378  point1.y = point2.y;
379  point2.y = tmp;
380  }
381 
382  //Send pixy the selected region
383  pixy_cc_set_region(1, point1.x, point1.y, point2.x - point1.x, point2.y - point1.y);
384  }
385 
386  //no break here: We want the following code to be executed as well
387 
388  case abortselecting: { //Abort-Selecting State: Where we deinitialize the stuff we used for region selection
389  touch_unregister_area(&a_area); //Remove the touch area. We'll no longer receive touch events
390 
391  b_select.text = "Select Color"; //Change the button text back to "Select Color"
392  gui_button_redraw(&b_select); //redraw button
393 
394  tracking_current->start(tracking_current); //Start tracking again
395  state = tracking;
396  }
397  break;
398 
399  case selecting: //Selecting State: Where we wait on the user to select a color region
400  pixy_service(); //receive pixy events
401  //wait on user to select the image area
402  break;
403 
404  case error: //Error State: Where we show an error message and leave the user no other choice than to click the backbutton
405  //wait on user to click the back button
406  break;
407  }
408 }
409 
410 //Declare screen callbacks
412  enter,
413  leave,
414  update
415 };
416 
417 
419 {
420  return &screen;
421 }
TOUCH_ACTION
Definition: touch.h:52
bool touch_register_area(TOUCH_AREA_STRUCT *area)
Definition: touch.c:181
const char * text
The label of the button.
Definition: button.h:61
void gui_button_redraw(BUTTON_STRUCT *button)
Definition: button.c:164
uint16_t txtcolor
The 16-bit text color.
Definition: button.h:59
#define AUTO
Use this value instead of x2, y2 in the BUTTON_STRUCT to autocalculate the button width/height...
Definition: button.h:65
Receive an event when the pen goes down inside the region.
Definition: touch.h:54
static void leave(void *screen)
Receive an event when the pen goes up inside the region.
Definition: touch.h:55
bool gui_checkbox_add(CHECKBOX_STRUCT *checkbox)
Definition: checkbox.c:70
static TRACKING_CONFIG_STRUCT tracking_our
uint16_t y1
Top Left Y-Coordinate of Area.
Definition: touch.h:75
uint16_t y
The Y-Coordinate of the point.
Definition: touch.h:88
#define FRAME_START_Y
bool gui_button_add(BUTTON_STRUCT *button)
Definition: button.c:133
void tft_print_line(uint16_t x, uint16_t y, uint16_t color, uint16_t bgcolor, uint8_t font, const char *text)
Definition: tft.c:98
void tracking_set_mode(enum Tracking_Implementation impl)
void(* TRACKING_VOID_CALLBACK)(void *tracking_config)
#define RGB(r, g, b)
Definition: tft.h:48
#define FRAME_HEIGHT
uint16_t x
The X-Coordinate of the point.
Definition: touch.h:87
void gui_checkbox_remove(CHECKBOX_STRUCT *checkbox)
Definition: checkbox.c:119
uint16_t x1
Top Left X-Coordinate of Area.
Definition: touch.h:74
uint16_t y
Definition: pixy.h:81
bool checked
A boolean which indicates whether or not the checkbox is currently checked.
Definition: checkbox.h:53
#define TRANSPARENT
Definition: tft.h:66
static TRACKING_CONFIG_STRUCT * tracking_current
uint16_t bgcolor
The 16-bit background color of the button.
Definition: button.h:57
TOUCH_AREA_STRUCT base
Basic geometry of the button. You only need to set the x1, y1, x2, y2 members of this struct...
Definition: button.h:56
void touch_unregister_area(TOUCH_AREA_STRUCT *area)
Definition: touch.c:195
Tracking_Implementation
void tracking_our_update(void *tracking_config, struct Block *blocks, int num_blocks)
#define HEX(h)
Definition: tft.h:60
int pixy_blocks_are_new()
Indicates when new block data from Pixy is received.
uint16_t y2
Bottom Right Y-Coordinate of Area.
Definition: touch.h:77
static void c_frame_toggle_cb(void *checkbox, bool checked)
int pixy_service()
Pixy's internal tracking implementation.
static void update(void *screen)
int16_t pixy_PID_X(int16_t x, int16_t w)
Definition: pixy_control.c:67
void tracking_reference_stop(void *tracking_config)
TOUCH_CALLBACK callback
Callback which is executed when an event occurred in this Area.
Definition: touch.h:78
void tracking_reference_update(void *tracking_config, struct Block *blocks, int num_blocks)
void tracking_reference_start(void *tracking_config)
int pixy_command(const char *name,...)
Send a command to Pixy.
void tft_clear(uint16_t color)
Definition: tft.c:45
uint16_t height
Definition: pixy.h:83
uint16_t x2
Bottom Right X-Coordinate of Area.
Definition: touch.h:76
TRACKING_VOID_CALLBACK start
#define CHECKBOX_WIN_FG_COLOR
Definition: checkbox.h:82
void gui_button_remove(BUTTON_STRUCT *button)
Definition: button.c:184
static BUTTON_STRUCT b_select
#define WHITE
Definition: tft.h:53
static POINT_STRUCT point1
TRACKING_VOID_CALLBACK stop
POINT_STRUCT touch_get_last_point()
Definition: touch.c:211
void tft_fill_rectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
Definition: tft.c:67
#define FRAME_END_Y
void(* TRACKING_BLOCK_CALLBACK)(void *tracking_config, struct Block *blocks, int num_blocks)
static TOUCH_AREA_STRUCT a_area
int pixy_init()
Creates a connection with Pixy and listens for Pixy messages.
static volatile bool frame_visible
uint16_t width
Definition: pixy.h:82
TOUCH_ACTION hookedActions
Actions to listen to.
Definition: touch.h:73
#define END_OUT_ARGS
Definition: pixydefs.h:89
int pixy_rcs_set_position(uint8_t channel, uint16_t position)
Set pixy servo axis position.
void tft_draw_rectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
Definition: tft.c:61
SCREEN_STRUCT * get_screen_tracking()
void tracking_our_stop(void *tracking_config)
static TRACKING_CONFIG_STRUCT tracking_reference
void tracking_our_start(void *tracking_config)
static SCREEN_STRUCT screen
static int16_t servo_y
static int16_t servo_x
static void enter(void *screen)
#define FRAME_WIDTH
TRACKING_BLOCK_CALLBACK update
TOUCH_AREA_STRUCT base
Basic geometry of the Checkbox. You only need to set the x1, y1, x2, y2 members of this struct...
Definition: checkbox.h:51
#define FRAME_END_X
int16_t pixy_PID_Y(int16_t x, int16_t w)
Definition: pixy_control.c:44
static void b_select_cb(void *button)
static bool point1_valid
static enum @2 state
uint16_t fgcolor
The 16-bit color of the tickmark.
Definition: checkbox.h:52
#define FRAME_START_X
static BUTTON_STRUCT b_back
BUTTON_CALLBACK callback
Callback which is executed when the button is pressed.
Definition: button.h:58
static CHECKBOX_STRUCT c_frame_toggle
int pixy_render_full_frame(uint16_t x, uint16_t y)
Definition: pixy_frame.c:25
bool gui_screen_back()
Definition: screen.c:85
int pixy_cc_set_region(uint8_t signum, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height)
Definition: pixy_frame.c:233
static POINT_STRUCT point2
int pixy_get_blocks(uint16_t max_blocks, struct Block *blocks)
Copies up to 'max_blocks' number of Blocks to the address pointed to by 'blocks'. ...
CHECKBOX_CALLBACK callback
Callback which is executed when the checkbox changes state.
Definition: checkbox.h:54
uint8_t font
The number of the font to use.
Definition: button.h:60
Our own tracking PID implementation.
int pixy_led_set_RGB(uint8_t red, uint8_t green, uint8_t blue)
Set color of pixy LED.
static void b_back_cb(void *button)
uint16_t x
Definition: pixy.h:80
Definition: pixy.h:53
static void touchCB(void *touchArea, TOUCH_ACTION triggeredAction)
#define END_IN_ARGS
Definition: pixydefs.h:90
#define BLACK
Definition: tft.h:54
#define INT8(v)
Definition: pixydefs.h:61