Merge remote-tracking branch 'origin/emulator' into dev_aaron
This commit is contained in:
241
common/app/pixy_helper.c
Normal file
241
common/app/pixy_helper.c
Normal file
@@ -0,0 +1,241 @@
|
||||
#include "pixy_helper.h"
|
||||
#include "pixy.h"
|
||||
#include "tft.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
|
||||
static int renderBA81(uint16_t xpos, uint16_t ypos, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame);
|
||||
static int saveBA81(FILE_HANDLE* handle, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame);
|
||||
|
||||
|
||||
int pixy_render_full_frame(uint16_t x, uint16_t y) {
|
||||
return pixy_render_cropped_frame(x,y,0,0,320,200);
|
||||
}
|
||||
|
||||
|
||||
int pixy_render_cropped_frame(uint16_t x, uint16_t y, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height) {
|
||||
uint8_t* videodata;
|
||||
int32_t response;
|
||||
int32_t fourccc;
|
||||
int8_t renderflags;
|
||||
uint16_t xwidth;
|
||||
uint16_t ywidth;
|
||||
uint32_t size;
|
||||
|
||||
|
||||
int return_value = pixy_command("cam_getFrame", // String id for remote procedure
|
||||
INT8(0x21), // mode
|
||||
INT16(xoffset), // xoffset
|
||||
INT16(yoffset), // yoffset
|
||||
INT16(width), // width
|
||||
INT16(height), // height
|
||||
END_OUT_ARGS, // separator
|
||||
&response, // pointer to mem address for return value
|
||||
&fourccc,
|
||||
&renderflags,
|
||||
&xwidth,
|
||||
&ywidth,
|
||||
&size,
|
||||
&videodata, // pointer to mem address for returned frame
|
||||
END_IN_ARGS);
|
||||
|
||||
if(return_value==0) {
|
||||
return_value = renderBA81(x,y,xwidth,ywidth,size,videodata);
|
||||
}
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
int pixy_save_full_frame(FILE_HANDLE* handle) {
|
||||
return pixy_save_cropped_frame(handle,0,0,320,200);
|
||||
}
|
||||
|
||||
int pixy_save_cropped_frame(FILE_HANDLE* handle, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height) {
|
||||
uint8_t* videodata;
|
||||
int32_t response;
|
||||
int32_t fourccc;
|
||||
int8_t renderflags;
|
||||
uint16_t xwidth;
|
||||
uint16_t ywidth;
|
||||
uint32_t size;
|
||||
|
||||
|
||||
int return_value = pixy_command("cam_getFrame", // String id for remote procedure
|
||||
INT8(0x21), // mode
|
||||
INT16(xoffset), // xoffset
|
||||
INT16(yoffset), // yoffset
|
||||
INT16(width), // width
|
||||
INT16(height), // height
|
||||
END_OUT_ARGS, // separator
|
||||
&response, // pointer to mem address for return value
|
||||
&fourccc,
|
||||
&renderflags,
|
||||
&xwidth,
|
||||
&ywidth,
|
||||
&size,
|
||||
&videodata, // pointer to mem address for returned frame
|
||||
END_IN_ARGS);
|
||||
|
||||
if(return_value==0) {
|
||||
return_value = saveBA81(handle,xwidth,ywidth,size,videodata);
|
||||
}
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void interpolateBayer(uint16_t width, uint16_t x, uint16_t y, uint8_t *pixel, uint8_t* r, uint8_t* g, uint8_t* b)
|
||||
{
|
||||
if (y&1)
|
||||
{
|
||||
if (x&1)
|
||||
{
|
||||
*r = *pixel;
|
||||
*g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2;
|
||||
*b = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2;
|
||||
}
|
||||
else
|
||||
{
|
||||
*r = (*(pixel-1)+*(pixel+1))>>1;
|
||||
*g = *pixel;
|
||||
*b = (*(pixel-width)+*(pixel+width))>>1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x&1)
|
||||
{
|
||||
*r = (*(pixel-width)+*(pixel+width))>>1;
|
||||
*g = *pixel;
|
||||
*b = (*(pixel-1)+*(pixel+1))>>1;
|
||||
}
|
||||
else
|
||||
{
|
||||
*r = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2;
|
||||
*g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2;
|
||||
*b = *pixel;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static int renderBA81(uint16_t xpos, uint16_t ypos, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame)
|
||||
{
|
||||
uint16_t x, y;
|
||||
uint8_t r, g, b;
|
||||
|
||||
|
||||
// skip first line
|
||||
frame += width;
|
||||
|
||||
// don't render top and bottom rows, and left and rightmost columns because of color
|
||||
// interpolation
|
||||
//uint32_t decodedimage[(width-2)*(height-2)];
|
||||
uint16_t* decodedimage = malloc(sizeof(uint16_t)*(width-2)*(height-2));
|
||||
|
||||
if(decodedimage==NULL) { //not enough free space to decode image in memory
|
||||
//decode & render image pixel by pixel
|
||||
for (y=1; y<height-1; y++)
|
||||
{
|
||||
frame++;
|
||||
for (x=1; x<width-1; x++, frame++)
|
||||
{
|
||||
interpolateBayer(width, x, y, frame, &r, &g, &b);
|
||||
tft_draw_pixel(xpos+x-1,ypos+y-1,RGB(r,g,b));
|
||||
}
|
||||
frame++;
|
||||
}
|
||||
} else { //enough space
|
||||
uint16_t* line = decodedimage;
|
||||
for (y=1; y<height-1; y++)
|
||||
{
|
||||
//line = (unsigned int *)img.scanLine(y-1);
|
||||
frame++;
|
||||
for (x=1; x<width-1; x++, frame++)
|
||||
{
|
||||
interpolateBayer(width, x, y, frame, &r, &g, &b);
|
||||
//*line++ = (0xff<<24) | (r<<16) | (g<<8) | (b<<0);
|
||||
*line++ = RGB(r,g,b);
|
||||
}
|
||||
frame++;
|
||||
}
|
||||
|
||||
tft_draw_bitmap_unscaled(xpos,ypos,width-2,height-2,decodedimage);
|
||||
|
||||
free(decodedimage);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int saveBA81(FILE_HANDLE* handle, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame)
|
||||
{
|
||||
uint16_t x, y;
|
||||
uint8_t r, g, b;
|
||||
|
||||
uint32_t fpos = handle->fpos;
|
||||
uint32_t row_size_padded = ((width-2)*3 + 3) & (~3); //row size aligned to 4 bytes
|
||||
uint32_t fpos_end = fpos + row_size_padded* (height-2);
|
||||
|
||||
|
||||
// skip first line
|
||||
frame += width;
|
||||
|
||||
// don't render top and bottom rows, and left and rightmost columns because of color
|
||||
// interpolation
|
||||
|
||||
for (y=1; y<height-1; y++)
|
||||
{
|
||||
frame++;
|
||||
uint8_t rowbuf[row_size_padded];
|
||||
|
||||
//Bitmaps are saved "bottom-up". Seek to the right row.
|
||||
if(filesystem_file_seek(handle,fpos_end-row_size_padded*y)!=F_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (x=1; x<width-1; x++, frame++)
|
||||
{
|
||||
interpolateBayer(width, x, y, frame, &r, &g, &b);
|
||||
//bitmaps are saved in 24bit b,g,r format
|
||||
rowbuf[(x-1)*3] = b;
|
||||
rowbuf[(x-1)*3+1] = g;
|
||||
rowbuf[(x-1)*3+2] = r;
|
||||
}
|
||||
|
||||
if(filesystem_file_write(handle,rowbuf,row_size_padded)!=F_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int pixy_cc_set_region(uint8_t signum, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height) {
|
||||
int32_t response;
|
||||
|
||||
int return_value = pixy_command("cc_setSigRegion", // String id for remote procedure
|
||||
INT32(0), // type = normal color code
|
||||
INT8(signum),
|
||||
INT16(xoffset), // xoffset
|
||||
INT16(yoffset), // yoffset
|
||||
INT16(width), // width
|
||||
INT16(height), // height
|
||||
END_OUT_ARGS, // separator
|
||||
&response, // pointer to mem address for return value
|
||||
END_IN_ARGS);
|
||||
return return_value;
|
||||
}
|
||||
|
||||
|
||||
59
common/app/pixy_helper.h
Normal file
59
common/app/pixy_helper.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef PIXY_HELPER_H
|
||||
#define PIXY_HELPER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "filesystem.h"
|
||||
|
||||
/**
|
||||
* Receives a fullsized frame from pixy and display's it on the display with the topleft corner at (x,y)
|
||||
* @param x The x-Coordinate of the top left corner
|
||||
* @param y The y-Coordinate of the top left corner
|
||||
* @return 0 on success, otherwise the errorcode from pixy
|
||||
*/
|
||||
int pixy_render_full_frame(uint16_t x, uint16_t y);
|
||||
|
||||
/**
|
||||
* Receives a cropped frame from pixy and display's it on the display with the topleft corner at (x,y)
|
||||
* @param x The x-Coordinate of the top left corner to draw the image
|
||||
* @param y The y-Coordinate of the top left corner to draw the image
|
||||
* @param xoffset The x-Coordinate on the pixy image from where on you want the frame data
|
||||
* @param yoffset The y-Coordinate on the pixy image from where on you want the frame data
|
||||
* @param width The width of the image recorded from pixy
|
||||
* @param height The height of the image recorded from pixy
|
||||
* @return 0 on success, otherwise the errorcode from pixy
|
||||
*/
|
||||
int pixy_render_cropped_frame(uint16_t x, uint16_t y, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height);
|
||||
|
||||
/**
|
||||
* Receives a fullsized frame from pixy and saves it to the given file in the 24bit (b,g,a) format.
|
||||
* Use this method to write the bitmap-data part of a windows bitmap (.bmp).
|
||||
* This method will neither open nor close the passed file.
|
||||
* @param handle The file to write the data to. The file must be open and it should be seeked to the right position.
|
||||
* @return 0 on success, otherwise the errorcode from pixy
|
||||
*/
|
||||
int pixy_save_full_frame(FILE_HANDLE* handle);
|
||||
|
||||
/**
|
||||
* Receives a cropped frame from pixy and saves it to the given file in the 24bit (b,g,a) format.
|
||||
* @param handle The file to write the data to. The file must be open and it should be seeked to the right position.
|
||||
* @param xoffset The x-Coordinate on the pixy image from where on you want the frame data
|
||||
* @param yoffset The y-Coordinate on the pixy image from where on you want the frame data
|
||||
* @param width The width of the image recorded from pixy
|
||||
* @param height The height of the image recorded from pixy
|
||||
* @return 0 on success, otherwise the errorcode from pixy
|
||||
*/
|
||||
int pixy_save_cropped_frame(FILE_HANDLE* handle, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height);
|
||||
|
||||
/**
|
||||
* Sets the color signature to the color in the selected region of the frame
|
||||
* @param signum the color signature number (1..7)
|
||||
* @param xoffset The x-Coordinate of the topleft point of the region
|
||||
* @param yoffset The y-Coordinate of the topleft point of the region
|
||||
* @param width The width of the region
|
||||
* @param height The height of the region
|
||||
* @return 0 on success, otherwise the errorcode from pixy
|
||||
*/
|
||||
int pixy_cc_set_region(uint8_t signum, uint16_t xoffset, uint16_t yoffset, uint16_t width, uint16_t height);
|
||||
|
||||
#endif /* PIXY_HELPER_H */
|
||||
@@ -70,6 +70,7 @@ static void enter(void* screen) {
|
||||
y+=14;
|
||||
}
|
||||
|
||||
filesystem_dir_close(dir);
|
||||
|
||||
y+=14;
|
||||
|
||||
@@ -129,46 +130,10 @@ SCREEN_STRUCT* get_screen_filetest() {
|
||||
}
|
||||
|
||||
static void image_test() {
|
||||
//Source: http://stackoverflow.com/a/17040962/2606757
|
||||
|
||||
FILE_HANDLE* file = filesystem_file_open("cpu.bmp");
|
||||
if(file==NULL) {
|
||||
|
||||
if(!tft_draw_bitmap_file_unscaled(250,170,"cpu.bmp")) {
|
||||
tft_print_line(10,180,BLUE,TRANSPARENT,0,"Could not open cpu.bmp");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
unsigned char info[54];
|
||||
if(filesystem_file_read(file,info,54)!=F_OK) {
|
||||
tft_print_line(10,180,BLUE,TRANSPARENT,0,"Could not read header of cpu.bmp");
|
||||
filesystem_file_close(file);
|
||||
return;
|
||||
}
|
||||
|
||||
// extract image height and width from header
|
||||
int width = *(int*)&info[18];
|
||||
int height = *(int*)&info[22];
|
||||
|
||||
filesystem_file_seek(file,*(int*)&info[10]);
|
||||
|
||||
unsigned char data [width*4];
|
||||
tft_draw_rectangle(100-1,160-1,100-1+width,160-1+height,BLACK);
|
||||
|
||||
for(int i = 0; i < height; i++)
|
||||
{
|
||||
filesystem_file_read(file,data,width*4);
|
||||
for(int j = 0; j < width*4; j += 4)
|
||||
{
|
||||
unsigned char a = data[j];
|
||||
unsigned char r = data[j+1];
|
||||
unsigned char g = data[j+2];
|
||||
unsigned char b = data[j+3];
|
||||
if(a!=0) {
|
||||
tft_draw_pixel(100+j/4,160+height-1-i,RGB(r,g,b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filesystem_file_close(file);
|
||||
|
||||
tft_draw_rectangle(250-1,170-1,250-1+64,170-1+64,BLACK);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* @defgroup filetest Filetest
|
||||
* @defgroup filetest Filetest (Screen)
|
||||
* The File-Test Screen tests the filesystem module. It read/writes from/to files and shows a bitmap
|
||||
*/
|
||||
/*@{*/
|
||||
@@ -19,4 +19,5 @@
|
||||
*/
|
||||
SCREEN_STRUCT* get_screen_filetest();
|
||||
|
||||
/*@}@}*/
|
||||
/*@}*/
|
||||
/*@}*/
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* @defgroup guitest Guitest
|
||||
* @defgroup guitest Guitest (Screen)
|
||||
* The Gui-Test Screen tests the gui and the tft module.
|
||||
*/
|
||||
/*@{*/
|
||||
@@ -20,4 +20,5 @@
|
||||
*/
|
||||
SCREEN_STRUCT* get_screen_guitest();
|
||||
|
||||
/*@}@}*/
|
||||
/*@}*/
|
||||
/*@}*/
|
||||
|
||||
@@ -2,13 +2,35 @@
|
||||
#include "screen_guitest.h"
|
||||
#include "screen_pixytest.h"
|
||||
#include "screen_filetest.h"
|
||||
#include "screen_photomode.h"
|
||||
#include "screen_tracking.h"
|
||||
#include "button.h"
|
||||
#include "tft.h"
|
||||
#include "filesystem.h"
|
||||
|
||||
BUTTON_STRUCT b_guitest;
|
||||
BUTTON_STRUCT b_pixytest;
|
||||
BUTTON_STRUCT b_filetest;
|
||||
|
||||
BUTTON_STRUCT b_our_tracking;
|
||||
BUTTON_STRUCT b_ref_tracking;
|
||||
BUTTON_STRUCT b_photo_mode;
|
||||
|
||||
|
||||
static void b_our_tracking_cb(void* button) {
|
||||
tracking_set_mode(OUR_TRACKING);
|
||||
gui_screen_navigate(get_screen_tracking());
|
||||
}
|
||||
|
||||
static void b_ref_tracking_cb(void* button) {
|
||||
tracking_set_mode(REFERENCE_TRACKING);
|
||||
gui_screen_navigate(get_screen_tracking());
|
||||
}
|
||||
|
||||
static void b_photo_mode_cb(void* button) {
|
||||
gui_screen_navigate(get_screen_photomode());
|
||||
}
|
||||
|
||||
static void b_guitest_cb(void* button) {
|
||||
gui_screen_navigate(get_screen_guitest());
|
||||
}
|
||||
@@ -25,47 +47,108 @@ static void b_pixytest_cb(void* button) {
|
||||
static void enter(void* screen) {
|
||||
tft_clear(WHITE);
|
||||
|
||||
//button to reach guitest
|
||||
b_guitest.base.x1=25; //Start X of Button
|
||||
b_guitest.base.y1=45; //Start Y of Button
|
||||
b_guitest.base.x2=AUTO; //b_guitest.base.x1+160; //Auto Calculate X2 with String Width
|
||||
b_guitest.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_guitest.txtcolor=WHITE; //Set foreground color
|
||||
b_guitest.bgcolor=HEX(0xDE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_guitest.font=0; //Select Font
|
||||
b_guitest.text="Gui/Tft Test"; //Set Text (For formatted strings take sprintf)
|
||||
b_guitest.callback=b_guitest_cb; //Call b_guitest_cb as Callback
|
||||
gui_button_add(&b_guitest); //Register Button (and run the callback from now on)
|
||||
//Heading
|
||||
tft_print_line(10,10,BLUE,TRANSPARENT,1,"Discoverpixy");
|
||||
tft_draw_line(0,40,319,40,BLACK);
|
||||
|
||||
//button to reach pixy test
|
||||
b_pixytest.base.x1=150; //Start X of Button
|
||||
b_pixytest.base.y1=45; //Start Y of Button
|
||||
b_pixytest.base.x2=AUTO; //b_pixytest.base.x1+160; //Auto Calculate X2 with String Width
|
||||
b_pixytest.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_pixytest.txtcolor=BLUE; //Set foreground color
|
||||
b_pixytest.bgcolor=HEX(0x10DE10); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_pixytest.font=0; //Select Font
|
||||
b_pixytest.text="Pixy Test"; //Set Text (For formatted strings take sprintf)
|
||||
b_pixytest.callback=b_pixytest_cb; //Call b_pixytest_cb as Callback
|
||||
gui_button_add(&b_pixytest); //Register Button (and run the callback from now on)
|
||||
#define X_TAB 90
|
||||
#define BUTTON_SPACING 7
|
||||
|
||||
//button to reach filesystem test
|
||||
b_filetest.base.x1=240; //Start X of Button
|
||||
b_filetest.base.y1=45; //Start Y of Button
|
||||
b_filetest.base.x2=AUTO; //b_filetest.base.x1+160; //Auto Calculate X2 with String Width
|
||||
b_filetest.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_filetest.txtcolor=WHITE; //Set foreground color
|
||||
b_filetest.bgcolor=HEX(0x501EA0); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_filetest.font=0; //Select Font
|
||||
b_filetest.text="File Test"; //Set Text (For formatted strings take sprintf)
|
||||
b_filetest.callback=b_filetest_cb; //Call b_filetest_cb as Callback
|
||||
gui_button_add(&b_filetest); //Register Button (and run the callback from now on)
|
||||
//First line of buttons
|
||||
#define Y_FIRST 60
|
||||
tft_print_line(10,Y_FIRST,BLACK,TRANSPARENT,0,"Tracking:");
|
||||
|
||||
b_our_tracking.base.x1=X_TAB; //Start X of Button
|
||||
b_our_tracking.base.y1=Y_FIRST-3; //Start Y of Button
|
||||
b_our_tracking.base.x2=AUTO; //Auto Calculate X2 with String Width
|
||||
b_our_tracking.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_our_tracking.txtcolor=WHITE; //Set foreground color
|
||||
b_our_tracking.bgcolor=HEX(0xE30535); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_our_tracking.font=0; //Select Font
|
||||
b_our_tracking.text="Our Tracking"; //Set Text (For formatted strings take sprintf)
|
||||
b_our_tracking.callback=b_our_tracking_cb; //Call b_our_tracking when the button get's pressed
|
||||
gui_button_add(&b_our_tracking); //Register Button (and run the callback from now on)
|
||||
|
||||
|
||||
b_ref_tracking.base.x1=b_our_tracking.base.x2+BUTTON_SPACING;
|
||||
b_ref_tracking.base.y1=Y_FIRST-3;
|
||||
b_ref_tracking.base.x2=AUTO;
|
||||
b_ref_tracking.base.y2=AUTO;
|
||||
b_ref_tracking.txtcolor=WHITE;
|
||||
b_ref_tracking.bgcolor=HEX(0xFF2151);
|
||||
b_ref_tracking.font=0;
|
||||
b_ref_tracking.text="Reference Tracking";
|
||||
b_ref_tracking.callback=b_ref_tracking_cb;
|
||||
gui_button_add(&b_ref_tracking);
|
||||
|
||||
//Second line of buttons
|
||||
#define Y_SECOND Y_FIRST+25
|
||||
tft_print_line(10,Y_SECOND,BLACK,TRANSPARENT,0,"Photo mode:");
|
||||
|
||||
b_photo_mode.base.x1=X_TAB;
|
||||
b_photo_mode.base.y1=Y_SECOND-3;
|
||||
b_photo_mode.base.x2=AUTO;
|
||||
b_photo_mode.base.y2=AUTO;
|
||||
b_photo_mode.txtcolor=WHITE;
|
||||
b_photo_mode.bgcolor=HEX(0x21B1FF);
|
||||
b_photo_mode.font=0;
|
||||
b_photo_mode.text="Photo Mode";
|
||||
b_photo_mode.callback=b_photo_mode_cb;
|
||||
gui_button_add(&b_photo_mode);
|
||||
|
||||
|
||||
//Third line of buttons
|
||||
#define Y_THIRD Y_SECOND+25
|
||||
tft_print_line(10,Y_THIRD,BLACK,TRANSPARENT,0,"Tests:");
|
||||
|
||||
b_guitest.base.x1=X_TAB;
|
||||
b_guitest.base.y1=Y_THIRD-3;
|
||||
b_guitest.base.x2=AUTO;
|
||||
b_guitest.base.y2=AUTO;
|
||||
b_guitest.txtcolor=BLACK;
|
||||
b_guitest.bgcolor=HEX(0x00FA21);
|
||||
b_guitest.font=0;
|
||||
b_guitest.text="Gui Test";
|
||||
b_guitest.callback=b_guitest_cb;
|
||||
gui_button_add(&b_guitest);
|
||||
|
||||
|
||||
b_pixytest.base.x1=b_guitest.base.x2+BUTTON_SPACING;
|
||||
b_pixytest.base.y1=Y_THIRD-3;
|
||||
b_pixytest.base.x2=AUTO;
|
||||
b_pixytest.base.y2=AUTO;
|
||||
b_pixytest.txtcolor=BLACK;
|
||||
b_pixytest.bgcolor=HEX(0x00FA96);
|
||||
b_pixytest.font=0;
|
||||
b_pixytest.text="Pixy Test";
|
||||
b_pixytest.callback=b_pixytest_cb;
|
||||
gui_button_add(&b_pixytest);
|
||||
|
||||
|
||||
b_filetest.base.x1=b_pixytest.base.x2+BUTTON_SPACING;
|
||||
b_filetest.base.y1=Y_THIRD-3;
|
||||
b_filetest.base.x2=AUTO;
|
||||
b_filetest.base.y2=AUTO;
|
||||
b_filetest.txtcolor=BLACK;
|
||||
b_filetest.bgcolor=HEX(0x00FAC4);
|
||||
b_filetest.font=0;
|
||||
b_filetest.text="File Test";
|
||||
b_filetest.callback=b_filetest_cb;
|
||||
gui_button_add(&b_filetest);
|
||||
|
||||
|
||||
//Bottom line
|
||||
tft_draw_line(0,145,319,145,BLACK);
|
||||
tft_print_line(10,150,BLUE,TRANSPARENT,0,"Powered by");
|
||||
tft_draw_bitmap_file_unscaled(10,165,"pixy_small.bmp");
|
||||
tft_draw_bitmap_file_unscaled(165,165,"stm_small.bmp");
|
||||
|
||||
}
|
||||
|
||||
static void leave(void* screen) {
|
||||
gui_button_remove(&b_our_tracking);
|
||||
gui_button_remove(&b_ref_tracking);
|
||||
gui_button_remove(&b_photo_mode);
|
||||
gui_button_remove(&b_guitest);
|
||||
gui_button_remove(&b_pixytest);
|
||||
gui_button_remove(&b_filetest);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* @defgroup main Main
|
||||
* @defgroup main Main (Screen)
|
||||
* The Main Screen is the start-screen for the application
|
||||
*/
|
||||
/*@{*/
|
||||
@@ -31,4 +31,5 @@
|
||||
*/
|
||||
SCREEN_STRUCT* get_screen_main();
|
||||
|
||||
/*@}@}*/
|
||||
/*@}*/
|
||||
/*@}*/
|
||||
|
||||
166
common/app/screen_photomode.c
Normal file
166
common/app/screen_photomode.c
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "screen_photomode.h"
|
||||
#include "screen_photomode_save.h"
|
||||
#include "button.h"
|
||||
#include "tft.h"
|
||||
#include "touch.h"
|
||||
#include "pixy.h"
|
||||
#include "system.h"
|
||||
#include "pixy_helper.h"
|
||||
|
||||
static bool pixy_connected = false; //Whether or not the pixy cam is currently connected
|
||||
|
||||
static BUTTON_STRUCT b_back; //Button to navigate back
|
||||
static BUTTON_STRUCT b_save; //Button to save the current image
|
||||
static TOUCH_AREA_STRUCT a_area; //Touch Area, where the frame is drawn. Used to drag the image around
|
||||
static bool subMenu=false; //Whether or not we left the current screen for a submenu
|
||||
|
||||
//Callback for when the user presses the "back" button
|
||||
static void b_back_cb(void* button) {
|
||||
subMenu = false; //we're not entering a submenu
|
||||
gui_screen_back(); //navigate back to the previous screen
|
||||
}
|
||||
|
||||
//Callback for when the user presses the "save" button
|
||||
static void b_save_cb(void* button) {
|
||||
subMenu = true; //we're entering a submenu
|
||||
gui_screen_navigate(get_screen_photomodesave()); //navigate to the save screen
|
||||
}
|
||||
|
||||
static POINT_STRUCT pixy_pos; //The current position of pixy's servos
|
||||
static POINT_STRUCT old_pos; //The last touch position on the screen
|
||||
|
||||
//Callback for when the user drags the image around
|
||||
static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) {
|
||||
POINT_STRUCT p = touch_get_last_point(); //get the last touched point
|
||||
switch(triggeredAction) {
|
||||
case PEN_ENTER:
|
||||
case PEN_DOWN:
|
||||
old_pos = p; //If the user "newly" enters the touch area, we set the "last" position to the current
|
||||
break;
|
||||
case PEN_MOVE: //the user is moving around, he entered the screen a while ago (old_pos is set)
|
||||
{
|
||||
int16_t deltaX = p.x - old_pos.x; //Calculate x difference between last and current touch
|
||||
int16_t deltaY = p.y - old_pos.y; //Calculate y difference between last and current touch
|
||||
old_pos=p; //store the current touch point for the next time
|
||||
//printf("%d %d\n",deltaX,deltaY);
|
||||
if(pixy_connected) {
|
||||
//Calculate new servo coordinates. 2 is just a proportional factor
|
||||
int16_t new_x = pixy_pos.x+deltaX*2;
|
||||
int16_t new_y = pixy_pos.y-deltaY*2;
|
||||
|
||||
//check limits
|
||||
if(new_x<0) new_x=0;
|
||||
if(new_x>1000) new_x=1000;
|
||||
if(new_y<0) new_y=0;
|
||||
if(new_y>1000) new_y=1000;
|
||||
|
||||
//set pixy_pos so that the main routine can send it to the servos
|
||||
pixy_pos.x = new_x;
|
||||
pixy_pos.y= new_y;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PEN_UP:
|
||||
case PEN_LEAVE:
|
||||
//printf("Leave/up\n");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Callback for when the screen is entered/loaded
|
||||
static void enter(void* screen) {
|
||||
tft_clear(WHITE);
|
||||
|
||||
tft_print_line(5,5,BLACK,TRANSPARENT,0,"Drag the image around and ");
|
||||
|
||||
//Back button
|
||||
b_back.base.x1=5; //Start X of Button
|
||||
b_back.base.y1=19; //Start Y of Button
|
||||
b_back.base.x2=AUTO; //Auto Calculate X2 with String Width
|
||||
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_back.txtcolor=WHITE; //Set foreground color
|
||||
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_back.font=0; //Select Font
|
||||
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
|
||||
b_back.callback=b_back_cb; //Call b_back_cb as Callback
|
||||
gui_button_add(&b_back); //Register Button (and run the callback from now on)
|
||||
|
||||
//Save button
|
||||
b_save.base.x1=190;
|
||||
b_save.base.y1=3;
|
||||
b_save.base.x2=AUTO;
|
||||
b_save.base.y2=AUTO;
|
||||
b_save.txtcolor=WHITE;
|
||||
b_save.bgcolor=HEX(0x1010AE);
|
||||
b_save.font=0;
|
||||
b_save.text="Save it!";
|
||||
b_save.callback=b_save_cb;
|
||||
gui_button_add(&b_save);
|
||||
|
||||
//Frame Coordinates: topleft = (1,40); bottomright = (318,238)
|
||||
//Leave a 10px border for the area
|
||||
|
||||
//Area to drag the image around
|
||||
a_area.hookedActions = PEN_DOWN | PEN_MOVE | PEN_ENTER | PEN_UP | PEN_LEAVE;
|
||||
a_area.x1 = 11;
|
||||
a_area.y1 = 50;
|
||||
a_area.x2 = 308;
|
||||
a_area.y2 = 228;
|
||||
a_area.callback = touchCB;
|
||||
touch_register_area(&a_area);
|
||||
|
||||
//Pixy stuff
|
||||
pixy_connected = (pixy_init()==0); //try to connect to pixy
|
||||
if(pixy_connected && !subMenu) { //pixy is connected, but we are not coming from a submenu
|
||||
pixy_pos.x=pixy_pos.y=500; //reset servo positions to center
|
||||
}
|
||||
}
|
||||
|
||||
//Callback for when the screen is left/unloaded
|
||||
static void leave(void* screen) {
|
||||
//remove buttons and touch area.
|
||||
gui_button_remove(&b_back);
|
||||
gui_button_remove(&b_save);
|
||||
touch_unregister_area(&a_area);
|
||||
}
|
||||
|
||||
//Callback for when the screen should be updated
|
||||
//This is the main loop of the screen. This method will be called repeatedly
|
||||
static void update(void* screen) {
|
||||
//Note: The only way to detect that pixy has been disconnected is if a command fails. There's no pixy_is_connected method yet :'(
|
||||
|
||||
if(!pixy_connected) { //Pixy not connected
|
||||
pixy_close(); //Ensure that all pixy resources are freed (failsafe)
|
||||
if(pixy_init()==0) { //try to connect to pixy
|
||||
pixy_connected=true;
|
||||
if(!subMenu) { //we're not coming from a submenu
|
||||
pixy_pos.x=pixy_pos.y=500; //reset servo positions to center
|
||||
}
|
||||
printf("pixy (re)initialized\n");
|
||||
}
|
||||
}
|
||||
|
||||
if(pixy_connected) { //If we are connected (now)
|
||||
pixy_service(); //Handle pending pixy events (e.g. color info retrival)
|
||||
|
||||
pixy_render_full_frame(1,40); //render the pixy video at point (1,40)
|
||||
|
||||
//set the servo positions to the coordinates form the touch interrupt
|
||||
pixy_rcs_set_position(0,pixy_pos.x);
|
||||
pixy_rcs_set_position(1,pixy_pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
//Declare screen callbacks
|
||||
static SCREEN_STRUCT screen = {
|
||||
enter,
|
||||
leave,
|
||||
update
|
||||
};
|
||||
|
||||
|
||||
SCREEN_STRUCT* get_screen_photomode() {
|
||||
return &screen;
|
||||
}
|
||||
22
common/app/screen_photomode.h
Normal file
22
common/app/screen_photomode.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "screen.h"
|
||||
|
||||
/**
|
||||
* @addtogroup screens
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* @defgroup photomode Photo Mode (Screen)
|
||||
* The Photo Mode Screen allows taking snapshots of the current pixy cam feed
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* Returns a pointer to the photomode screen
|
||||
* \sa gui_screen_navigate
|
||||
* @return
|
||||
*/
|
||||
SCREEN_STRUCT* get_screen_photomode();
|
||||
|
||||
/*@}*/
|
||||
/*@}*/
|
||||
301
common/app/screen_photomode_save.c
Normal file
301
common/app/screen_photomode_save.c
Normal file
@@ -0,0 +1,301 @@
|
||||
#include "screen_photomode_save.h"
|
||||
#include "filesystem.h"
|
||||
#include "button.h"
|
||||
#include "tft.h"
|
||||
#include "touch.h"
|
||||
#include "pixy.h"
|
||||
#include "pixy_helper.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
static BUTTON_STRUCT b_back; //Button to navigate back
|
||||
static TOUCH_AREA_STRUCT a_area; //Touch area to select the save-file
|
||||
|
||||
//Callback for when the user presses the "back" button
|
||||
static void b_back_cb(void* button) {
|
||||
gui_screen_back();
|
||||
}
|
||||
|
||||
static int num_files_ok; //number of files into which we can write the image (size, flags ok)
|
||||
static enum {init, error, showlist, picking, saving, done} state; //Current state of the screen state machine
|
||||
static int fontheight; //The space between one line of text to the next
|
||||
static int liststart; //The y-Coordinate of the Start of the File-List
|
||||
static const char* picked_file; //The filename picked by the user, to save the image to
|
||||
|
||||
//Linked list structure to save all files which are suitable for saving.
|
||||
typedef struct FILE_LIST_ENTRY_S{
|
||||
char* filename; //Name of the file
|
||||
struct FILE_LIST_ENTRY_S* next; //Pointer to the next entry in the list or NULL
|
||||
} FILE_LIST_ENTRY;
|
||||
|
||||
static FILE_LIST_ENTRY* files_ok; //Pointer to the head of the list
|
||||
|
||||
//Callback for when the user selects a file to save the image into
|
||||
static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) {
|
||||
|
||||
int y = touch_get_last_point().y-liststart; //Calculate the y-Coordinate of the touch point relative to the start of the file-list
|
||||
int elem = y/fontheight; //Calculate the file index
|
||||
if(elem<0 | elem>= num_files_ok) return; //Check if the file index is valid (0,1,..,num_files_ok-1)
|
||||
|
||||
//Search for the corresponding entry in the linked list
|
||||
FILE_LIST_ENTRY* current_entry = files_ok; //Start walking through the list, starting by the head of the list
|
||||
for(int i=0; i<elem; i++) { //Until we have reached the file (index)
|
||||
current_entry= current_entry->next; //traverse to the next file
|
||||
}
|
||||
|
||||
picked_file = current_entry->filename; //save the picked filename. It will be used by the statemachine in the main loop
|
||||
touch_unregister_area(&a_area); //unregister the touch area, we no longer need it. No more interrupts will be fired.
|
||||
state=saving; //Change the state of the statemachine
|
||||
}
|
||||
|
||||
//Text-Lines to show if we have no matching files (num_files_ok==0)
|
||||
static const char* nomatch_text [] = {
|
||||
"Due to limitations of the filesystem",
|
||||
"implementation you can only write to",
|
||||
"existing files.",
|
||||
"",
|
||||
"The files need to have a .bmp",
|
||||
"extension and must be at least",
|
||||
"189410 bytes (185kb) large.",
|
||||
"Unfortunately there were no such",
|
||||
"files found in the root directory.",
|
||||
"",
|
||||
"Please create some files and come",
|
||||
"back again.",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
//Bitmap header for a 318x198x24bit windows bitmap. data starts at 0x7A (= after this header)
|
||||
//This header has been taken from a white bitmap saved with gimp.
|
||||
//Wikipedia has a pretty good description on the header: http://de.wikipedia.org/wiki/Windows_Bitmap
|
||||
static unsigned char bmpheader_data[0x7A] = {
|
||||
0x42, 0x4d, 0xe2, 0xe3, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x00,
|
||||
0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x3e, 0x01, 0x00, 0x00, 0xc6, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xe3,
|
||||
0x02, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x47, 0x52, 0x73, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00
|
||||
};
|
||||
|
||||
//Callback for when the screen is entered/loaded
|
||||
static void enter(void* screen) {
|
||||
tft_clear(WHITE);
|
||||
|
||||
|
||||
#define X_OFS 5
|
||||
|
||||
//Back button
|
||||
b_back.base.x1=X_OFS; //Start X of Button
|
||||
b_back.base.y1=210; //Start Y of Button
|
||||
b_back.base.x2=AUTO; //Auto Calculate X2 with String Width
|
||||
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_back.txtcolor=WHITE; //Set foreground color
|
||||
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_back.font=0; //Select Font
|
||||
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
|
||||
b_back.callback=b_back_cb; //Call b_back_cb as Callback
|
||||
gui_button_add(&b_back); //Register Button (and run the callback from now on)
|
||||
|
||||
state =init; //Start with the init state
|
||||
fontheight = tft_font_height(0)+2; //Save the height of the used font, for fast access
|
||||
files_ok = NULL; //initialize the linked list with 0 elements
|
||||
num_files_ok = 0; //we have zero! elements
|
||||
}
|
||||
|
||||
//Callback for when the screen should be updated
|
||||
//This is the main loop of the screen. This method will be called repeatedly
|
||||
static void update(void* screen) {
|
||||
switch(state) {
|
||||
case init: //Init State: The user just entered the screen
|
||||
{
|
||||
DIRECTORY_STRUCT* dir = filesystem_dir_open("."); //open root directory
|
||||
if(dir==NULL) { //error while opening root directory
|
||||
tft_print_line(X_OFS,5,BLACK,TRANSPARENT,0,"Error accessing Filesystem");
|
||||
state=error;
|
||||
break;
|
||||
}
|
||||
|
||||
bool nomatch= true; //whether or not we have zero files which are suitable for saving
|
||||
|
||||
for(int i=0; i<dir->num_files; i++) { //walk through all files in the directory
|
||||
FILE_STRUCT* file = &(dir->files[i]); //Pointer to the current file/subdirectory
|
||||
|
||||
//Ignore directories, archives, hidden files, system files and files we cannot write to
|
||||
if(file->fattrib&(F_SYS|F_HID|F_ARC|F_DIR|F_RDO)) continue;
|
||||
|
||||
//ignore files which are not large enough
|
||||
if(file->fsize<189410) continue; //size taken from an example bitmap (318x198x24)
|
||||
|
||||
nomatch=false; //at least one file matches
|
||||
break;
|
||||
}
|
||||
|
||||
if(nomatch) { //not one file is suitable for writing
|
||||
int y=5; //y-Coordinate where to start writing the error text
|
||||
int i=0;
|
||||
while(nomatch_text[i]!=NULL) { //for every line in the big error array
|
||||
//Write the line's text and go to the next line
|
||||
tft_print_line(X_OFS,y+i*fontheight,BLACK,TRANSPARENT,0,nomatch_text[i]);
|
||||
i++;
|
||||
}
|
||||
state = error;
|
||||
} else { //we have a least one suitable file
|
||||
state = showlist;
|
||||
}
|
||||
|
||||
filesystem_dir_close(dir); //free directory struct
|
||||
}
|
||||
break;
|
||||
|
||||
case showlist: //Show List State: Where we load and present the suitable file's to the user in a list
|
||||
{
|
||||
DIRECTORY_STRUCT* dir2 = filesystem_dir_open("."); //Open the directory again
|
||||
if(dir2==NULL) return; //Error on opening? This should never happen, since it's handled in the previous state
|
||||
|
||||
int y = 5; //y-Coordinate where to start drawing/writing text/list-elements
|
||||
|
||||
tft_print_line(X_OFS,y,BLACK,TRANSPARENT,0,"Pick a file to save the image to");
|
||||
y+=fontheight+5;
|
||||
|
||||
tft_print_line(X_OFS,y,BLUE,TRANSPARENT,0,"Name Modified Size");
|
||||
y+=fontheight;
|
||||
|
||||
liststart = y; //store the y coordinate of the start of the list away (used in toucharea callback)
|
||||
num_files_ok = 0; //we start with 0 matching files
|
||||
|
||||
FILE_LIST_ENTRY* current_entry = NULL; //We start with an empty list
|
||||
for(int i=0; i<dir2->num_files && num_files_ok<10; i++) { //go through all the files of the directory, abort if we have 10 matches
|
||||
FILE_STRUCT* file = &(dir2->files[i]);
|
||||
|
||||
//Ignore directories, archives, hidden files, system files and files we cannot write to
|
||||
if(file->fattrib&(F_SYS|F_HID|F_ARC|F_DIR|F_RDO)) continue;
|
||||
|
||||
//ignore files which are not large enough
|
||||
if(file->fsize<189410) continue; //size taken from an example bitmap (318x198x24)
|
||||
|
||||
//Print out filename, modified date,time and file size
|
||||
tft_print_formatted(X_OFS,y,BLACK,
|
||||
TRANSPARENT,0,"%-16s %02u.%02u.%02u %02u:%02u:%02u %u",
|
||||
file->fname,
|
||||
file->fdate.day,
|
||||
file->fdate.month,
|
||||
(file->fdate.year+1980)%100,
|
||||
file->ftime.hour,
|
||||
file->ftime.min,
|
||||
file->ftime.sec*2,
|
||||
file->fsize);
|
||||
|
||||
if(current_entry==NULL) { //The list is empty
|
||||
current_entry = malloc(sizeof(FILE_LIST_ENTRY)); //create new entry
|
||||
files_ok = current_entry; //assign it to the list head
|
||||
} else { //there's a least one entry in the list
|
||||
current_entry->next = malloc(sizeof(FILE_LIST_ENTRY)); //append entry to previous entry
|
||||
current_entry = current_entry->next; //newly created entry is the current now.
|
||||
}
|
||||
current_entry->next = NULL; //we're at the end of the list (for now)
|
||||
current_entry->filename = malloc(strlen(file->fname)+1); //allocate space for the filename + zero-termination
|
||||
strcpy(current_entry->filename,file->fname); //copy filename (so that we can close the directory after scanning)
|
||||
|
||||
//since we have found a suitable file we need to increment the position in the list
|
||||
num_files_ok++;
|
||||
y+=fontheight;
|
||||
}
|
||||
|
||||
//Touch area for file-selection (in the list)
|
||||
a_area.hookedActions = PEN_UP; //we're only interested in PEN_UP events
|
||||
a_area.x1 = X_OFS; //Left border
|
||||
a_area.y1 = liststart; //Start where the list started
|
||||
a_area.x2 = 320-X_OFS; //Right border
|
||||
a_area.y2 = liststart+fontheight*num_files_ok; //stop at the end of the list
|
||||
a_area.callback = touchCB; //execute our callback when PEN_UP occurs
|
||||
touch_register_area(&a_area); //register the touch area and receive events from now on
|
||||
|
||||
filesystem_dir_close(dir2); //we no longer need the directory struct, since we have our own linked list now
|
||||
|
||||
state=picking;
|
||||
}
|
||||
break;
|
||||
|
||||
case picking: //Picking State: Where we wait on the users file choice
|
||||
pixy_service(); //Handle pending pixy events
|
||||
//do nothing and wait on user to pick a file
|
||||
break;
|
||||
|
||||
case saving: //Saving State: Where we save the image to the selected file
|
||||
{
|
||||
FILE_HANDLE* file = filesystem_file_open(picked_file); //try to open the selected file
|
||||
if(file==NULL) { //opening the file failed
|
||||
tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Could not open %s",picked_file);
|
||||
state=error;
|
||||
break;
|
||||
}
|
||||
|
||||
filesystem_file_seek(file,0); //seek to the start of the file (optional?)
|
||||
if(filesystem_file_write(file,bmpheader_data,0x7A)!=F_OK) { //Writing the header failed
|
||||
tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Error while writing to %s",picked_file);
|
||||
filesystem_file_close(file);
|
||||
state=error;
|
||||
break;
|
||||
}
|
||||
|
||||
if(pixy_save_full_frame(file)!=0) { //Writing the imagedata failed
|
||||
tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Error while writing to %s",picked_file);
|
||||
filesystem_file_close(file);
|
||||
state=error;
|
||||
break;
|
||||
}
|
||||
|
||||
//if we reach this point, we have written all data out successfully
|
||||
|
||||
filesystem_file_close(file); //close/finalize the file
|
||||
tft_print_formatted(X_OFS,190,BLUE,TRANSPARENT,0,"Image saved to %s",picked_file);
|
||||
state = done;
|
||||
}
|
||||
break;
|
||||
|
||||
case error: //Error State: Where we show an error message and leave the user no other choice than to click the backbutton
|
||||
case done: //Done State: When saving the file was successful
|
||||
pixy_service(); //Handle pending pixy events
|
||||
//wait on user to click the back button
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Callback for when the screen is left/unloaded
|
||||
static void leave(void* screen) {
|
||||
gui_button_remove(&b_back); //Remove/Free the back button
|
||||
|
||||
if(state==picking){ //The user left the screen in the "picking"-phase
|
||||
touch_unregister_area(&a_area); //remove the touch area (for the list)
|
||||
}
|
||||
|
||||
if(state==picking|| state==saving || state==done) { //the user left the screen after we created the linked list
|
||||
//Iterate through the linked list and free all resources
|
||||
FILE_LIST_ENTRY* current_entry = files_ok; //start with the list head
|
||||
while(current_entry!=NULL) { //while we're not at the end
|
||||
FILE_LIST_ENTRY* temp = current_entry->next; //save the next pointer because we free the current element on the next line
|
||||
free((void*)(current_entry->filename)); //free filename
|
||||
free(current_entry); //free element itself
|
||||
current_entry= temp; //advance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Declare screen callbacks
|
||||
static SCREEN_STRUCT screen = {
|
||||
enter,
|
||||
leave,
|
||||
update
|
||||
};
|
||||
|
||||
SCREEN_STRUCT* get_screen_photomodesave() {
|
||||
return &screen;
|
||||
}
|
||||
23
common/app/screen_photomode_save.h
Normal file
23
common/app/screen_photomode_save.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "screen.h"
|
||||
|
||||
/**
|
||||
* @addtogroup screens
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* @defgroup photomodesave Photo Mode Save (Screen)
|
||||
* The Photo Mode Save Screen helps the user saving a file to the filesystem
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* Returns a pointer to the photomode save screen
|
||||
* \sa gui_screen_navigate
|
||||
* @return
|
||||
*/
|
||||
SCREEN_STRUCT* get_screen_photomodesave();
|
||||
|
||||
/*@}*/
|
||||
/*@}*/
|
||||
|
||||
@@ -1,173 +1,329 @@
|
||||
#include "screen_pixytest.h"
|
||||
#include "button.h"
|
||||
#include "numupdown.h"
|
||||
#include "tft.h"
|
||||
#include "touch.h"
|
||||
#include "pixy.h"
|
||||
#include <stdlib.h>
|
||||
#include "system.h"
|
||||
#include "pixy_helper.h"
|
||||
|
||||
static volatile bool pixy_connected = false;
|
||||
|
||||
static volatile enum {detecting, idle,update_servos, update_ledcolor, update_ledcurrent} state; //Current state of the screen state machine
|
||||
|
||||
static BUTTON_STRUCT b_back;
|
||||
static BUTTON_STRUCT b_runstop;
|
||||
static TOUCH_AREA_STRUCT a_area;
|
||||
|
||||
static BUTTON_STRUCT b_servos_center;
|
||||
static BUTTON_STRUCT b_servos_topleft;
|
||||
static BUTTON_STRUCT b_servos_topright;
|
||||
static BUTTON_STRUCT b_servos_bottomleft;
|
||||
static BUTTON_STRUCT b_servos_bottomright;
|
||||
static uint16_t servo_x;
|
||||
static uint16_t servo_y;
|
||||
|
||||
static BUTTON_STRUCT b_led_off;
|
||||
static BUTTON_STRUCT b_led_white;
|
||||
static BUTTON_STRUCT b_led_red;
|
||||
static BUTTON_STRUCT b_led_green;
|
||||
static BUTTON_STRUCT b_led_blue;
|
||||
static uint32_t led_color;
|
||||
|
||||
static uint32_t led_maxcurrent;
|
||||
static NUMUPDOWN_STRUCT n_led_powerlimit;
|
||||
|
||||
|
||||
static void b_back_cb(void* button) {
|
||||
gui_screen_back();
|
||||
}
|
||||
|
||||
|
||||
static volatile bool pixy_running = false;
|
||||
static bool old_pixy_running= false;
|
||||
static void b_runstop_cb(void* button) {
|
||||
pixy_running=!pixy_running;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static POINT_STRUCT pixy_pos;
|
||||
static POINT_STRUCT old_pos;
|
||||
static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) {
|
||||
POINT_STRUCT p = touch_get_last_point();
|
||||
switch(triggeredAction) {
|
||||
case PEN_ENTER:
|
||||
case PEN_DOWN:
|
||||
old_pos = p;
|
||||
break;
|
||||
case PEN_MOVE:
|
||||
{
|
||||
int16_t deltaX = p.x - old_pos.x;
|
||||
int16_t deltaY = p.y - old_pos.y;
|
||||
old_pos=p;
|
||||
printf("%d %d\n",deltaX,deltaY);
|
||||
if(pixy_connected) {
|
||||
int16_t new_x = pixy_pos.x+deltaX*2;
|
||||
int16_t new_y = pixy_pos.y-deltaY*2;
|
||||
if(new_x<0) new_x=0;
|
||||
if(new_x>1000) new_x=1000;
|
||||
if(new_y<0) new_y=0;
|
||||
if(new_y>1000) new_y=1000;
|
||||
pixy_pos.x = new_x;
|
||||
pixy_pos.y= new_y;
|
||||
}
|
||||
static void b_servos_center_cb(void* button) {
|
||||
if(state==idle) {
|
||||
servo_x=500;
|
||||
servo_y=500;
|
||||
state=update_servos;
|
||||
}
|
||||
break;
|
||||
case PEN_UP:
|
||||
case PEN_LEAVE:
|
||||
printf("Leave/up\n");
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void b_servos_topleft_cb(void* button) {
|
||||
if(state==idle) {
|
||||
servo_x=0;
|
||||
servo_y=0;
|
||||
state=update_servos;
|
||||
}
|
||||
}
|
||||
|
||||
static void b_servos_topright_cb(void* button) {
|
||||
if(state==idle) {
|
||||
servo_x=1000;
|
||||
servo_y=0;
|
||||
state=update_servos;
|
||||
}
|
||||
}
|
||||
|
||||
static void b_servos_bottomleft_cb(void* button) {
|
||||
if(state==idle) {
|
||||
servo_x=0;
|
||||
servo_y=1000;
|
||||
state=update_servos;
|
||||
}
|
||||
}
|
||||
|
||||
static void b_servos_bottomright_cb(void* button) {
|
||||
if(state==idle) {
|
||||
servo_x=1000;
|
||||
servo_y=1000;
|
||||
state=update_servos;
|
||||
}
|
||||
}
|
||||
|
||||
static void b_led_off_cb(void* button) {
|
||||
if(state==idle) {
|
||||
led_color=0x000000;
|
||||
state=update_ledcolor;
|
||||
}
|
||||
}
|
||||
|
||||
static void b_led_white_cb(void* button) {
|
||||
if(state==idle) {
|
||||
led_color=0xFFFFFF;
|
||||
state=update_ledcolor;
|
||||
}
|
||||
}
|
||||
|
||||
static void b_led_red_cb(void* button) {
|
||||
if(state==idle) {
|
||||
led_color=0xFF0000;
|
||||
state=update_ledcolor;
|
||||
}
|
||||
}
|
||||
|
||||
static void b_led_green_cb(void* button) {
|
||||
if(state==idle) {
|
||||
led_color=0x00FF00;
|
||||
state=update_ledcolor;
|
||||
}
|
||||
}
|
||||
|
||||
static void b_led_blue_cb(void* button) {
|
||||
if(state==idle) {
|
||||
led_color=0x0000FF;
|
||||
state=update_ledcolor;
|
||||
}
|
||||
}
|
||||
|
||||
static void n_led_powerlimit_cb(void* numupdown, int16_t value) {
|
||||
if(state==idle) {
|
||||
led_maxcurrent=value;
|
||||
state=update_ledcurrent;
|
||||
}
|
||||
}
|
||||
|
||||
static void enter(void* screen) {
|
||||
tft_clear(WHITE);
|
||||
|
||||
//Back button
|
||||
b_back.base.x1=10; //Start X of Button
|
||||
b_back.base.y1=210; //Start Y of Button
|
||||
b_back.base.x2=AUTO; //b_back.base.x1+160; //Auto Calculate X2 with String Width
|
||||
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_back.txtcolor=WHITE; //Set foreground color
|
||||
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_back.font=0; //Select Font
|
||||
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
|
||||
b_back.callback=b_back_cb; //Call b_back_cb as Callback
|
||||
gui_button_add(&b_back); //Register Button (and run the callback from now on)
|
||||
b_back.base.y1=210; //Start Y of Button
|
||||
b_back.base.x2=AUTO; //Auto Calculate X2 with String Width
|
||||
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_back.txtcolor=WHITE; //Set foreground color
|
||||
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_back.font=0; //Select Font
|
||||
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
|
||||
b_back.callback=b_back_cb; //Call b_back_cb as Callback
|
||||
gui_button_add(&b_back); //Register Button (and run the callback from now on)
|
||||
|
||||
|
||||
//Back button
|
||||
b_runstop.base.x1=60; //Start X of Button
|
||||
b_runstop.base.y1=210; //Start Y of Button
|
||||
b_runstop.base.x2=AUTO; //b_runstop.base.x1+160; //Auto Calculate X2 with String Width
|
||||
b_runstop.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_runstop.txtcolor=WHITE; //Set foreground color
|
||||
b_runstop.bgcolor=HEX(0xAE1010); //Set runstopground color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_runstop.font=0; //Select Font
|
||||
b_runstop.text="Run/Stop"; //Set Text (For formatted strings take sprintf)
|
||||
b_runstop.callback=b_runstop_cb; //Call b_runstop_cb as Callrunstop
|
||||
gui_button_add(&b_runstop); //Register Button (and run the callrunstop from now on)
|
||||
//Servo stuff
|
||||
#define SERVO_BUTTON_Y 10
|
||||
#define SERVO_BUTTON_SPACING 5
|
||||
tft_print_line(5,SERVO_BUTTON_Y,BLACK,TRANSPARENT,0,"Servos:");
|
||||
|
||||
b_servos_center.base.x1=55;
|
||||
b_servos_center.base.y1=SERVO_BUTTON_Y-3;
|
||||
b_servos_center.base.x2=AUTO;
|
||||
b_servos_center.base.y2=AUTO;
|
||||
b_servos_center.txtcolor=WHITE;
|
||||
b_servos_center.bgcolor=HEX(0xAE1010);
|
||||
b_servos_center.font=0;
|
||||
b_servos_center.text="Center";
|
||||
b_servos_center.callback=b_servos_center_cb;
|
||||
gui_button_add(&b_servos_center);
|
||||
|
||||
//Area test
|
||||
a_area.hookedActions = PEN_DOWN | PEN_MOVE | PEN_ENTER | PEN_UP | PEN_LEAVE;
|
||||
a_area.x1 = 0;
|
||||
a_area.y1 = 0;
|
||||
a_area.x2 = 317;
|
||||
a_area.y2 = 197;
|
||||
a_area.callback = touchCB;
|
||||
touch_register_area(&a_area);
|
||||
b_servos_topleft.base.x1=b_servos_center.base.x2+SERVO_BUTTON_SPACING;
|
||||
b_servos_topleft.base.y1=SERVO_BUTTON_Y-3;
|
||||
b_servos_topleft.base.x2=AUTO;
|
||||
b_servos_topleft.base.y2=AUTO;
|
||||
b_servos_topleft.txtcolor=WHITE;
|
||||
b_servos_topleft.bgcolor=HEX(0xAE1010);
|
||||
b_servos_topleft.font=0;
|
||||
b_servos_topleft.text="ToLe";
|
||||
b_servos_topleft.callback=b_servos_topleft_cb;
|
||||
gui_button_add(&b_servos_topleft);
|
||||
|
||||
b_servos_topright.base.x1=b_servos_topleft.base.x2+SERVO_BUTTON_SPACING;
|
||||
b_servos_topright.base.y1=SERVO_BUTTON_Y-3;
|
||||
b_servos_topright.base.x2=AUTO;
|
||||
b_servos_topright.base.y2=AUTO;
|
||||
b_servos_topright.txtcolor=WHITE;
|
||||
b_servos_topright.bgcolor=HEX(0xAE1010);
|
||||
b_servos_topright.font=0;
|
||||
b_servos_topright.text="ToRi";
|
||||
b_servos_topright.callback=b_servos_topright_cb;
|
||||
gui_button_add(&b_servos_topright);
|
||||
|
||||
b_servos_bottomleft.base.x1=b_servos_topright.base.x2+SERVO_BUTTON_SPACING;
|
||||
b_servos_bottomleft.base.y1=SERVO_BUTTON_Y-3;
|
||||
b_servos_bottomleft.base.x2=AUTO;
|
||||
b_servos_bottomleft.base.y2=AUTO;
|
||||
b_servos_bottomleft.txtcolor=WHITE;
|
||||
b_servos_bottomleft.bgcolor=HEX(0xAE1010);
|
||||
b_servos_bottomleft.font=0;
|
||||
b_servos_bottomleft.text="BoLe";
|
||||
b_servos_bottomleft.callback=b_servos_bottomleft_cb;
|
||||
gui_button_add(&b_servos_bottomleft);
|
||||
|
||||
b_servos_bottomright.base.x1=b_servos_bottomleft.base.x2+SERVO_BUTTON_SPACING;
|
||||
b_servos_bottomright.base.y1=SERVO_BUTTON_Y-3;
|
||||
b_servos_bottomright.base.x2=AUTO;
|
||||
b_servos_bottomright.base.y2=AUTO;
|
||||
b_servos_bottomright.txtcolor=WHITE;
|
||||
b_servos_bottomright.bgcolor=HEX(0xAE1010);
|
||||
b_servos_bottomright.font=0;
|
||||
b_servos_bottomright.text="BoRi";
|
||||
b_servos_bottomright.callback=b_servos_bottomright_cb;
|
||||
gui_button_add(&b_servos_bottomright);
|
||||
|
||||
//Led Color stuff
|
||||
#define LED_COLOR_BUTTON_Y 35
|
||||
#define LED_COLOR_BUTTON_SPACING 5
|
||||
tft_print_line(5,LED_COLOR_BUTTON_Y,BLACK,TRANSPARENT,0,"Led Color:");
|
||||
|
||||
b_led_off.base.x1=85;
|
||||
b_led_off.base.y1=LED_COLOR_BUTTON_Y-3;
|
||||
b_led_off.base.x2=AUTO;
|
||||
b_led_off.base.y2=AUTO;
|
||||
b_led_off.txtcolor=WHITE;
|
||||
b_led_off.bgcolor=BLACK;
|
||||
b_led_off.font=0;
|
||||
b_led_off.text="Off";
|
||||
b_led_off.callback=b_led_off_cb;
|
||||
gui_button_add(&b_led_off);
|
||||
|
||||
b_led_white.base.x1=b_led_off.base.x2+LED_COLOR_BUTTON_SPACING;
|
||||
b_led_white.base.y1=LED_COLOR_BUTTON_Y-3;
|
||||
b_led_white.base.x2=AUTO;
|
||||
b_led_white.base.y2=AUTO;
|
||||
b_led_white.txtcolor=BLACK;
|
||||
b_led_white.bgcolor=HEX(0xEEEEEE);
|
||||
b_led_white.font=0;
|
||||
b_led_white.text="White";
|
||||
b_led_white.callback=b_led_white_cb;
|
||||
gui_button_add(&b_led_white);
|
||||
|
||||
b_led_red.base.x1=b_led_white.base.x2+LED_COLOR_BUTTON_SPACING;
|
||||
b_led_red.base.y1=LED_COLOR_BUTTON_Y-3;
|
||||
b_led_red.base.x2=AUTO;
|
||||
b_led_red.base.y2=AUTO;
|
||||
b_led_red.txtcolor=WHITE;
|
||||
b_led_red.bgcolor=HEX(0xEE0000);
|
||||
b_led_red.font=0;
|
||||
b_led_red.text="Red";
|
||||
b_led_red.callback=b_led_red_cb;
|
||||
gui_button_add(&b_led_red);
|
||||
|
||||
b_led_green.base.x1=b_led_red.base.x2+LED_COLOR_BUTTON_SPACING;
|
||||
b_led_green.base.y1=LED_COLOR_BUTTON_Y-3;
|
||||
b_led_green.base.x2=AUTO;
|
||||
b_led_green.base.y2=AUTO;
|
||||
b_led_green.txtcolor=WHITE;
|
||||
b_led_green.bgcolor=HEX(0x00EE00);
|
||||
b_led_green.font=0;
|
||||
b_led_green.text="Green";
|
||||
b_led_green.callback=b_led_green_cb;
|
||||
gui_button_add(&b_led_green);
|
||||
|
||||
b_led_blue.base.x1=b_led_green.base.x2+LED_COLOR_BUTTON_SPACING;
|
||||
b_led_blue.base.y1=LED_COLOR_BUTTON_Y-3;
|
||||
b_led_blue.base.x2=AUTO;
|
||||
b_led_blue.base.y2=AUTO;
|
||||
b_led_blue.txtcolor=WHITE;
|
||||
b_led_blue.bgcolor=HEX(0x0000EE);
|
||||
b_led_blue.font=0;
|
||||
b_led_blue.text="Blue";
|
||||
b_led_blue.callback=b_led_blue_cb;
|
||||
gui_button_add(&b_led_blue);
|
||||
|
||||
//Led MaxPower stuff
|
||||
#define LED_POWER_BUTTON_Y 70
|
||||
tft_print_line(5,LED_POWER_BUTTON_Y,BLACK,TRANSPARENT,0,"Led Maximum Current:");
|
||||
|
||||
//Num up down test
|
||||
n_led_powerlimit.x=160;
|
||||
n_led_powerlimit.y=LED_POWER_BUTTON_Y-7;
|
||||
n_led_powerlimit.fgcolor=WHITE;
|
||||
n_led_powerlimit.value = 10;
|
||||
n_led_powerlimit.max=40;
|
||||
n_led_powerlimit.min =0;
|
||||
n_led_powerlimit.callback=n_led_powerlimit_cb;
|
||||
gui_numupdown_add(&n_led_powerlimit);
|
||||
|
||||
|
||||
|
||||
//Pixy stuff
|
||||
pixy_connected = (pixy_init()==0); //try to connect to pixy
|
||||
if(pixy_connected) {
|
||||
pixy_pos.x=pixy_pos.y=500;
|
||||
}
|
||||
state=detecting;
|
||||
}
|
||||
|
||||
static void leave(void* screen) {
|
||||
gui_button_remove(&b_back);
|
||||
gui_button_remove(&b_runstop);
|
||||
touch_unregister_area(&a_area);
|
||||
}
|
||||
gui_button_remove(&b_servos_center);
|
||||
gui_button_remove(&b_servos_topleft);
|
||||
gui_button_remove(&b_servos_topright);
|
||||
gui_button_remove(&b_servos_bottomleft);
|
||||
gui_button_remove(&b_servos_bottomright);
|
||||
gui_button_remove(&b_led_off);
|
||||
gui_button_remove(&b_led_white);
|
||||
gui_button_remove(&b_led_red);
|
||||
gui_button_remove(&b_led_green);
|
||||
gui_button_remove(&b_led_blue);
|
||||
gui_numupdown_remove(&n_led_powerlimit);
|
||||
|
||||
int pixy_led_test();
|
||||
int pixy_frame_test();
|
||||
}
|
||||
|
||||
|
||||
static void update(void* screen) {
|
||||
|
||||
//Note: The only way to detect that pixy has been disconnected is if a command fails. There's no pixy_is_connected method yet :'(
|
||||
|
||||
if(!pixy_connected) { //Pixy not connected
|
||||
pixy_close(); //Ensure that all pixy resources are freed (failsafe)
|
||||
if(pixy_init()==0) { //try to connect to pixy
|
||||
pixy_connected=true;
|
||||
pixy_pos.x=pixy_pos.y=500;
|
||||
printf("pixy reinitialized\n");
|
||||
}
|
||||
}
|
||||
|
||||
if(pixy_connected) {
|
||||
pixy_service(); //Send/receive event data from/to pixy failed
|
||||
|
||||
|
||||
if(pixy_frame_test()!=0) {
|
||||
pixy_connected=false;
|
||||
}
|
||||
|
||||
/*if(pixy_led_test()!=0) {
|
||||
pixy_connected=false;
|
||||
}*/
|
||||
|
||||
if(!pixy_running) {
|
||||
pixy_rcs_set_position(0,pixy_pos.x);
|
||||
pixy_rcs_set_position(1,pixy_pos.y);
|
||||
}
|
||||
|
||||
if(pixy_running!=old_pixy_running) {
|
||||
old_pixy_running=pixy_running;
|
||||
if(pixy_running) { //start tracking
|
||||
|
||||
int32_t response;
|
||||
int return_value;
|
||||
return_value = pixy_command("runprog", INT8(2), END_OUT_ARGS, &response, END_IN_ARGS);
|
||||
|
||||
} else { //stop tracking
|
||||
switch(state) {
|
||||
case detecting: //Detecting State: Where we try to connect to the pixy
|
||||
if(pixy_init()==0) { //Pixy connection ok
|
||||
int32_t response;
|
||||
int return_value;
|
||||
return_value = pixy_command("stop", END_OUT_ARGS, &response, END_IN_ARGS);
|
||||
pixy_led_set_max_current(10);
|
||||
|
||||
state = idle; //Go to next state
|
||||
}
|
||||
break;
|
||||
case idle:
|
||||
pixy_service();
|
||||
break;
|
||||
case update_servos:
|
||||
pixy_rcs_set_position(0,servo_x);
|
||||
pixy_rcs_set_position(1,servo_y);
|
||||
state = idle;
|
||||
break;
|
||||
|
||||
case update_ledcolor:
|
||||
{
|
||||
int32_t response;
|
||||
int return_value;
|
||||
return_value = pixy_command("led_set", INT32(led_color), END_OUT_ARGS, &response, END_IN_ARGS);
|
||||
state = idle;
|
||||
}
|
||||
|
||||
//system_delay(500);
|
||||
}
|
||||
break;
|
||||
|
||||
case update_ledcurrent:
|
||||
pixy_led_set_max_current(led_maxcurrent);
|
||||
state = idle;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -184,157 +340,3 @@ SCREEN_STRUCT* get_screen_pixytest() {
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
int colorind;
|
||||
const uint32_t colors [] = {0xFF0000, 0x00FF00,0x0000FF,0xFFFF00,0x00FFFF,0xFF00FF,0xFFFFFF,0x000000};
|
||||
const int num_colors = sizeof(colors)/sizeof(uint32_t);
|
||||
|
||||
int pixy_led_test() {
|
||||
if(colorind==0) {
|
||||
pixy_led_set_max_current(5);
|
||||
}
|
||||
|
||||
int32_t response;
|
||||
int return_value;
|
||||
return_value = pixy_command("led_set", INT32(colors[colorind++]), END_OUT_ARGS, &response, END_IN_ARGS);
|
||||
colorind%=num_colors;
|
||||
|
||||
if(return_value!=0) {
|
||||
colorind=0; //reset color ind, to start at zero when plugging pixy in again
|
||||
}
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
int renderBA81(uint8_t renderFlags, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame);
|
||||
|
||||
|
||||
int pixy_frame_test() {
|
||||
|
||||
uint8_t* videodata;
|
||||
int32_t response;
|
||||
int32_t fourccc;
|
||||
int8_t renderflags;
|
||||
uint16_t xwidth;
|
||||
uint16_t ywidth;
|
||||
uint32_t size;
|
||||
|
||||
|
||||
int return_value = pixy_command("cam_getFrame", // String id for remote procedure
|
||||
INT8(0x21), // mode
|
||||
INT16(0), // xoffset
|
||||
INT16(0), // yoffset
|
||||
INT16(320), // width
|
||||
INT16(200), // height
|
||||
END_OUT_ARGS, // separator
|
||||
&response, // pointer to mem address for return value
|
||||
&fourccc,
|
||||
&renderflags,
|
||||
&xwidth,
|
||||
&ywidth,
|
||||
&size,
|
||||
&videodata, // pointer to mem address for returned frame
|
||||
END_IN_ARGS);
|
||||
|
||||
if(return_value==0) {
|
||||
return_value = renderBA81(renderflags,xwidth,ywidth,size,videodata);
|
||||
}
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void interpolateBayer(uint16_t width, uint16_t x, uint16_t y, uint8_t *pixel, uint8_t* r, uint8_t* g, uint8_t* b)
|
||||
{
|
||||
if (y&1)
|
||||
{
|
||||
if (x&1)
|
||||
{
|
||||
*r = *pixel;
|
||||
*g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2;
|
||||
*b = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2;
|
||||
}
|
||||
else
|
||||
{
|
||||
*r = (*(pixel-1)+*(pixel+1))>>1;
|
||||
*g = *pixel;
|
||||
*b = (*(pixel-width)+*(pixel+width))>>1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x&1)
|
||||
{
|
||||
*r = (*(pixel-width)+*(pixel+width))>>1;
|
||||
*g = *pixel;
|
||||
*b = (*(pixel-1)+*(pixel+1))>>1;
|
||||
}
|
||||
else
|
||||
{
|
||||
*r = (*(pixel-width-1)+*(pixel-width+1)+*(pixel+width-1)+*(pixel+width+1))>>2;
|
||||
*g = (*(pixel-1)+*(pixel+1)+*(pixel+width)+*(pixel-width))>>2;
|
||||
*b = *pixel;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int renderBA81(uint8_t renderFlags, uint16_t width, uint16_t height, uint32_t frameLen, uint8_t *frame)
|
||||
{
|
||||
uint16_t x, y;
|
||||
uint8_t r, g, b;
|
||||
|
||||
|
||||
// skip first line
|
||||
frame += width;
|
||||
|
||||
// don't render top and bottom rows, and left and rightmost columns because of color
|
||||
// interpolation
|
||||
//uint32_t decodedimage[(width-2)*(height-2)];
|
||||
uint16_t* decodedimage = malloc(sizeof(uint16_t)*(width-2)*(height-2));
|
||||
|
||||
if(decodedimage==NULL) { //not enough free space to decode image in memory
|
||||
//decode & render image pixel by pixel
|
||||
uint16_t* line = decodedimage;
|
||||
for (y=1; y<height-1; y++)
|
||||
{
|
||||
frame++;
|
||||
for (x=1; x<width-1; x++, frame++)
|
||||
{
|
||||
interpolateBayer(width, x, y, frame, &r, &g, &b);
|
||||
tft_draw_pixel(x-1,y-1,RGB(r,g,b));
|
||||
}
|
||||
frame++;
|
||||
}
|
||||
} else { //enough space
|
||||
uint16_t* line = decodedimage;
|
||||
for (y=1; y<height-1; y++)
|
||||
{
|
||||
//line = (unsigned int *)img.scanLine(y-1);
|
||||
frame++;
|
||||
for (x=1; x<width-1; x++, frame++)
|
||||
{
|
||||
interpolateBayer(width, x, y, frame, &r, &g, &b);
|
||||
//*line++ = (0xff<<24) | (r<<16) | (g<<8) | (b<<0);
|
||||
*line++ = RGB(r,g,b);
|
||||
}
|
||||
frame++;
|
||||
}
|
||||
|
||||
tft_draw_bitmap_unscaled(0,0,width-2,height-2,decodedimage);
|
||||
|
||||
free(decodedimage);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* @defgroup pixytest Pixytest
|
||||
* @defgroup pixytest Pixytest (Screen)
|
||||
* The Pixy-Test Screen tests the pixy module.
|
||||
*/
|
||||
/*@{*/
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
SCREEN_STRUCT* get_screen_pixytest();
|
||||
|
||||
/*@}@}*/
|
||||
/*@}*/
|
||||
/*@}*/
|
||||
|
||||
349
common/app/screen_tracking.c
Normal file
349
common/app/screen_tracking.c
Normal file
@@ -0,0 +1,349 @@
|
||||
#include "screen_tracking.h"
|
||||
#include "button.h"
|
||||
#include "checkbox.h"
|
||||
#include "tft.h"
|
||||
#include "touch.h"
|
||||
#include "pixy.h"
|
||||
#include "system.h"
|
||||
#include "pixy_helper.h"
|
||||
|
||||
static BUTTON_STRUCT b_back; //Button to navigate back
|
||||
static BUTTON_STRUCT b_select; //Button to start the color region selection
|
||||
static CHECKBOX_STRUCT c_frame_toggle; //Checkbox to toggle video data on/off
|
||||
static TOUCH_AREA_STRUCT a_area; //Touch area for the color region selection
|
||||
|
||||
//Callback for when the user presses the "back" button
|
||||
static void b_back_cb(void* button) {
|
||||
gui_screen_back(); //navigate back to the previous screen
|
||||
}
|
||||
|
||||
static volatile bool frame_visible = false; //Whether or not the video data should be displayed
|
||||
static void c_frame_toggle_cb(void *checkbox, bool checked) {
|
||||
frame_visible=checked; //Set the visibility of the frame to the checked state of the checkbox
|
||||
//Frame will be drawn in the main loop below
|
||||
}
|
||||
|
||||
static enum {detecting, init, tracking, preselecting, abortselecting, selecting, selected, error} state; //Current state of the screen state machine
|
||||
|
||||
static POINT_STRUCT point1; //First point of the rectangle selected by the user (color region selection)
|
||||
static POINT_STRUCT point2; //End point of the rectangle selected by the user (color region selection)
|
||||
static bool point1_valid; //Whether or not we have a valid first point
|
||||
|
||||
//Callback for when the user presses the "select color" button
|
||||
static void b_select_cb(void* button) {
|
||||
if(state==selecting) { //we're currently selecting a color region
|
||||
state = abortselecting; //Abort selecting!!
|
||||
} else if (state==tracking) { //we're currently watching the tracking
|
||||
state = preselecting; //start selecting
|
||||
}
|
||||
}
|
||||
|
||||
//Video Region properties
|
||||
//The camera records with 320*200px, but we need to keep a 1px border because of color interpolation (bayer format)
|
||||
#define FRAME_START_X 1 //x-Coordinate of the top-left point of the frame rectangle on display
|
||||
#define FRAME_START_Y 41 //y-Coordinate of the top-left point of the frame rectangle on display
|
||||
#define FRAME_WIDTH 318 //Width of the video frame
|
||||
#define FRAME_HEIGHT 198 //Height of the video frame
|
||||
#define FRAME_END_X FRAME_START_X +FRAME_WIDTH-1 //x-Coordinate of the bottom-right point of the frame rectangle
|
||||
#define FRAME_END_Y FRAME_START_Y +FRAME_HEIGHT-1 //y-Coordinate of the bottom-right point of the frame rectangle
|
||||
|
||||
//Callback for when the user touches the frame area to select a color region.
|
||||
//Note: It doesn't matter in which direction the user draws the rectangle, we'll normalize the coordinates later
|
||||
static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction) {
|
||||
POINT_STRUCT p = touch_get_last_point();
|
||||
switch(triggeredAction) {
|
||||
case PEN_DOWN: //The user just put down the pen
|
||||
point1.x = p.x-FRAME_START_X; //Calculate x-Coordinate relative to frame start
|
||||
point1.y = p.y-FRAME_START_Y; //Calculate y-Coordinate relative to frame start
|
||||
point1_valid= true; //The point1 is now valid
|
||||
break;
|
||||
case PEN_UP: //The user took the pen away
|
||||
if(point1_valid) { //only execute if point1 is valid
|
||||
point2.x = p.x-FRAME_START_X; //Calculate x-Coordinate relative to frame start
|
||||
point2.y = p.y-FRAME_START_Y; //Calculate y-Coordinate relative to frame start
|
||||
state = selected;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Prototype for tracking start/stop methods
|
||||
typedef void (*TRACKING_VOID_CALLBACK)(void* tracking_config);
|
||||
//Prototype for tracking update method
|
||||
typedef void (*TRACKING_BLOCK_CALLBACK)(void* tracking_config, struct Block* blocks, int num_blocks );
|
||||
|
||||
//Structure to save callbacks and settings of a tracking implementation
|
||||
typedef struct {
|
||||
TRACKING_VOID_CALLBACK start;
|
||||
TRACKING_VOID_CALLBACK stop;
|
||||
TRACKING_BLOCK_CALLBACK update;
|
||||
} TRACKING_CONFIG_STRUCT;
|
||||
|
||||
//Methods for our tracking implementation ahead
|
||||
|
||||
//Method/Callback to start our tracking
|
||||
void tracking_our_start(void* tracking_config) {
|
||||
//Activate pixy's data send program
|
||||
int32_t response;
|
||||
int return_value;
|
||||
return_value = pixy_command("runprog", INT8(0), END_OUT_ARGS, &response, END_IN_ARGS);
|
||||
}
|
||||
|
||||
//Method/Callback to stop our tracking
|
||||
void tracking_our_stop(void* tracking_config) {
|
||||
//Stop pixy's data send programm
|
||||
int32_t response;
|
||||
int return_value;
|
||||
return_value = pixy_command("stop", END_OUT_ARGS, &response, END_IN_ARGS);
|
||||
}
|
||||
|
||||
//Method/Callback to calculate one step of our tracking
|
||||
void tracking_our_update(void* tracking_config, struct Block* blocks, int num_blocks) {
|
||||
//TODO: Implement tracking!
|
||||
//Calculate new servo pos and set the new servo pos
|
||||
}
|
||||
|
||||
//Variable which stores all the callbacks and settings for our tracking implementation
|
||||
static TRACKING_CONFIG_STRUCT tracking_our = {
|
||||
tracking_our_start,
|
||||
tracking_our_stop,
|
||||
tracking_our_update
|
||||
};
|
||||
|
||||
//Methods for reference tracking implementation ahead
|
||||
|
||||
//Method/Callback to start reference tracking
|
||||
void tracking_reference_start(void* tracking_config) {
|
||||
//Run reference tracking
|
||||
int32_t response;
|
||||
int return_value;
|
||||
return_value = pixy_command("runprog", INT8(2), END_OUT_ARGS, &response, END_IN_ARGS);
|
||||
}
|
||||
|
||||
//Method/Callback to stop reference tracking
|
||||
void tracking_reference_stop(void* tracking_config) {
|
||||
//Stop reference tracking
|
||||
int32_t response;
|
||||
int return_value;
|
||||
return_value = pixy_command("stop", END_OUT_ARGS, &response, END_IN_ARGS);
|
||||
}
|
||||
|
||||
//Method/Callback to calculate one step of the reference tracking
|
||||
void tracking_reference_update(void* tracking_config, struct Block* blocks, int num_blocks) {
|
||||
//Nothing to do here. Pixy does it all.
|
||||
}
|
||||
|
||||
//Variable which stores all the callbacks and settings for the reference tracking implementation
|
||||
static TRACKING_CONFIG_STRUCT tracking_reference = {
|
||||
tracking_reference_start,
|
||||
tracking_reference_stop,
|
||||
tracking_reference_update
|
||||
};
|
||||
|
||||
//Pointer to the currently active tracking implementation. See also tracking_set_mode
|
||||
static TRACKING_CONFIG_STRUCT* tracking_current;
|
||||
|
||||
//Method to set the current tracking implementation. This function is exported and should be called before getting the screen
|
||||
void tracking_set_mode(enum Tracking_Implementation impl) {
|
||||
//Depending on the enum value let tracking_current point to a different setting/callback structure
|
||||
switch(impl) {
|
||||
case OUR_TRACKING:
|
||||
tracking_current = &tracking_our;
|
||||
break;
|
||||
case REFERENCE_TRACKING:
|
||||
tracking_current = &tracking_reference;
|
||||
break;
|
||||
default:
|
||||
tracking_current=NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Callback for when the screen is entered/loaded
|
||||
static void enter(void* screen) {
|
||||
tft_clear(WHITE);
|
||||
|
||||
//"Back" button
|
||||
b_back.base.x1=5; //Start X of Button
|
||||
b_back.base.y1=5; //Start Y of Button
|
||||
b_back.base.x2=AUTO; //Auto Calculate X2 with String Width
|
||||
b_back.base.y2=AUTO; //Auto Calculate Y2 with String Height
|
||||
b_back.txtcolor=WHITE; //Set foreground color
|
||||
b_back.bgcolor=HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
||||
b_back.font=0; //Select Font
|
||||
b_back.text="Back"; //Set Text (For formatted strings take sprintf)
|
||||
b_back.callback=b_back_cb; //Call b_back_cb as Callback
|
||||
gui_button_add(&b_back); //Register Button (and run the callback from now on)
|
||||
|
||||
|
||||
//"Select color" button
|
||||
b_select.base.x1=150;
|
||||
b_select.base.y1=5;
|
||||
b_select.base.x2=AUTO;
|
||||
b_select.base.y2=AUTO;
|
||||
b_select.txtcolor=WHITE;
|
||||
b_select.bgcolor=HEX(0xAE1010);
|
||||
b_select.font=0;
|
||||
b_select.text="Select Color";
|
||||
b_select.callback=b_select_cb;
|
||||
gui_button_add(&b_select);
|
||||
|
||||
//"Frame visible" checkbox
|
||||
c_frame_toggle.base.x1 = 50;
|
||||
c_frame_toggle.base.x2 = 50+16;
|
||||
c_frame_toggle.base.y1 = 5;
|
||||
c_frame_toggle.base.y2 = 5+16;
|
||||
c_frame_toggle.checked = frame_visible;
|
||||
c_frame_toggle.fgcolor = CHECKBOX_WIN_FG_COLOR;
|
||||
c_frame_toggle.callback = c_frame_toggle_cb;
|
||||
gui_checkbox_add(&c_frame_toggle);
|
||||
tft_print_line(73,8,BLACK,TRANSPARENT,0,"Show Video");
|
||||
|
||||
|
||||
//Area to select a "color region"
|
||||
a_area.hookedActions = PEN_DOWN | PEN_UP;
|
||||
a_area.x1 = FRAME_START_X;
|
||||
a_area.y1 = FRAME_START_Y;
|
||||
a_area.x2 = FRAME_END_X;
|
||||
a_area.y2 = FRAME_END_Y;
|
||||
a_area.callback = touchCB;
|
||||
//Do not register it here, we do that later
|
||||
|
||||
if(tracking_current==NULL) {
|
||||
state = error;
|
||||
} else {
|
||||
state = detecting; //Start with the detecting state
|
||||
}
|
||||
}
|
||||
|
||||
//Callback for when the screen is left/unloaded
|
||||
static void leave(void* screen) {
|
||||
//Remove buttons and checkbox
|
||||
gui_button_remove(&b_back);
|
||||
gui_button_remove(&b_select);
|
||||
gui_checkbox_remove(&c_frame_toggle);
|
||||
|
||||
if(state==selecting) { //the user left the screen in the "selecting" phase
|
||||
touch_unregister_area(&a_area); //remove the touch area
|
||||
}
|
||||
|
||||
if(state==tracking) { //the user left the screen in the "tracking" phase
|
||||
tracking_current->stop(tracking_current); //stop tracking
|
||||
pixy_led_set_RGB(0,0,0);
|
||||
}
|
||||
}
|
||||
|
||||
//Callback for when the screen should be updated
|
||||
//This is the main loop of the screen. This method will be called repeatedly
|
||||
static void update(void* screen) {
|
||||
switch(state) {
|
||||
case detecting: //Detecting State: Where we try to connect to the pixy
|
||||
if(pixy_init()==0) { //Pixy connection ok
|
||||
state = init; //Go to next state
|
||||
}
|
||||
break;
|
||||
|
||||
case init: //Init State: Where we start the tracking
|
||||
tracking_current->start(tracking_current);
|
||||
state=tracking;
|
||||
break;
|
||||
|
||||
case tracking: //Tracking state: Where we render the frame and the tracked objects
|
||||
pixy_service(); //Receive events (e.g. block-data) from pixy
|
||||
|
||||
if(pixy_blocks_are_new()) { //There are new blocks available
|
||||
if(frame_visible) { //If the user want's us to draw the video data
|
||||
pixy_render_full_frame(FRAME_START_X,FRAME_START_Y);
|
||||
} else { //the user want's a colored background
|
||||
tft_fill_rectangle(FRAME_START_X,FRAME_START_Y,FRAME_END_X,FRAME_END_Y,RGB(200,200,200));
|
||||
}
|
||||
|
||||
#define BLOCK_BUFFER_SIZE 5 //The maximum amount of blocks that we want to receive
|
||||
struct Block blocks[BLOCK_BUFFER_SIZE]; //Storage to receive blocks from pixy
|
||||
int blocks_received= pixy_get_blocks(BLOCK_BUFFER_SIZE,blocks); //Try to receive up to BLOCK_BUFFER_SIZE Blocks from pixy
|
||||
|
||||
if(blocks_received>=0) { //block receiving ok
|
||||
tracking_current->update(tracking_current,blocks,blocks_received); //apply tracking
|
||||
|
||||
//Draw blocks
|
||||
for(int i=0; i<blocks_received; i++) { //for each received block
|
||||
struct Block* block = &(blocks[i]);
|
||||
//block.x and block.y are the center coordinates of the object relative to the camera origin.
|
||||
uint16_t x = block->x-1+FRAME_START_X -block->width/2; //Calculate x-Coordinate on the display
|
||||
uint16_t y = block->y-1+FRAME_START_Y -block->height/2; //Calculate y-Coordinate on the display
|
||||
tft_draw_rectangle(x,y,x+block->width-1, y+block->height-1,WHITE); //Draw a white rectangle
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case preselecting: //Pre-Selecting State: Where we set up the color region selection
|
||||
{
|
||||
tracking_current->stop(tracking_current); //Stop tracking
|
||||
|
||||
pixy_render_full_frame(FRAME_START_X,FRAME_START_Y); //Render one frame
|
||||
|
||||
touch_register_area(&a_area); //Register touch area and receive events from now on
|
||||
point1_valid=false; //we start with an invalid point1
|
||||
|
||||
b_select.text="Abort"; //Change the button text to "Abort"
|
||||
gui_button_redraw(&b_select); //redraw button
|
||||
|
||||
state = selecting; //The user can now select a region
|
||||
}
|
||||
break;
|
||||
|
||||
case selected: //Selected State: Where we send the users selection to pixy
|
||||
{
|
||||
//Ensure that (x1,y1) represent the top-left point and (x2,y2) the bottom-right.
|
||||
unsigned int tmp;
|
||||
if(point1.x > point2.x){
|
||||
tmp = point1.x;
|
||||
point1.x = point2.x;
|
||||
point2.x = tmp;
|
||||
}
|
||||
|
||||
if(point1.y > point2.y){
|
||||
tmp = point1.y;
|
||||
point1.y = point2.y;
|
||||
point2.y = tmp;
|
||||
}
|
||||
//Send pixy the selected region
|
||||
pixy_cc_set_region(1,point1.x,point1.y,point2.x-point1.x,point2.y-point1.y);
|
||||
}
|
||||
//no break here: We want the following code to be executed as well
|
||||
|
||||
case abortselecting: //Abort-Selecting State: Where we deinitialize the stuff we used for region selection
|
||||
{
|
||||
touch_unregister_area(&a_area); //Remove the touch area. We'll no longer receive touch events
|
||||
|
||||
b_select.text="Select Color"; //Change the button text back to "Select Color"
|
||||
gui_button_redraw(&b_select); //redraw button
|
||||
|
||||
tracking_current->start(tracking_current); //Start tracking again
|
||||
state=tracking;
|
||||
}
|
||||
break;
|
||||
|
||||
case selecting: //Selecting State: Where we wait on the user to select a color region
|
||||
pixy_service(); //receive pixy events
|
||||
//wait on user to select the image area
|
||||
break;
|
||||
|
||||
case error: //Error State: Where we show an error message and leave the user no other choice than to click the backbutton
|
||||
//wait on user to click the back button
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Declare screen callbacks
|
||||
static SCREEN_STRUCT screen = {
|
||||
enter,
|
||||
leave,
|
||||
update
|
||||
};
|
||||
|
||||
|
||||
SCREEN_STRUCT* get_screen_tracking() {
|
||||
return &screen;
|
||||
}
|
||||
37
common/app/screen_tracking.h
Normal file
37
common/app/screen_tracking.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "screen.h"
|
||||
|
||||
/**
|
||||
* @addtogroup screens
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* @defgroup tracking Tracking (Screen)
|
||||
* The Tracking-Screen shows the object-tracking and allows some configuration
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
|
||||
/**
|
||||
* Enum which contains the available tracking implementations
|
||||
*/
|
||||
enum Tracking_Implementation {
|
||||
OUR_TRACKING, //!< Our own tracking PID implementation
|
||||
REFERENCE_TRACKING//!< Pixy's internal tracking implementation
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the current Mode/Tracking Implementation. Call this before using the screen obtained by get_screen_tracking()
|
||||
* @param impl The new mode
|
||||
*/
|
||||
void tracking_set_mode(enum Tracking_Implementation impl);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the tracking screen
|
||||
* \sa gui_screen_navigate
|
||||
* @return
|
||||
*/
|
||||
SCREEN_STRUCT* get_screen_tracking();
|
||||
|
||||
/*@}*/
|
||||
/*@}*/
|
||||
Reference in New Issue
Block a user