Update screenshot client and server sketches

Server can now provide a filepath/name,  define saved image file type
and specify filename extension method.  Max number of images saved by
client per session limited to 1000. A pause/run button has been added.
This commit is contained in:
Bodmer
2017-03-13 02:31:59 +00:00
parent 0fe1acf6cb
commit 4d0488a906
4 changed files with 403 additions and 108 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -20,8 +20,8 @@
// option "Show Sketch Folder" or press Ctrl+K
// Created by: Bodmer 5/3/17
// Updated by: Bodmer 10/3/17
// Version: 0.06
// Updated by: Bodmer 12/3/17
// Version: 0.07
// MIT licence applies, all text above must be included in derivative works
@@ -45,7 +45,9 @@ boolean save_border = true; // Save the image with a border
int border = 5; // Border pixel width #
boolean fade = false; // Fade out image after saving #
// #
int max_images = 10; // Maximum of numbered saved images before over-writing files #
int max_images = 100; // Maximum of numbered file images before over-writing files #
// #
int max_allowed = 1000; // Maximum number of save images allowed before a restart #
// #
// # End of the values to change for a particular setup #
// ###########################################################################################
@@ -69,6 +71,13 @@ color bgcolor2 = color(77, 183, 187); // Arduino IDE style background color
// TFT image frame greyscale value (dark grey)
color frameColor = 42;
color buttonStopped = color(255, 0, 0);
color buttonRunning = color(128, 204, 206);
color buttonDimmed = color(180, 0, 0);
boolean dimmed = false;
boolean running = true;
boolean mouseClick = false;
int[] rgb = new int[3]; // Buffer for the colour bytes
int indexRed = 0; // Colour byte index in the array
int indexGreen = 1;
@@ -85,6 +94,9 @@ int beginTime = 0;
int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive
int lastPixelTime = 0; // Time that "image send" command was sent
int requestTime = 0;
int requestCount = 0;
int state = 0; // State machine current state
int progress_bar = 0; // Console progress bar dot count
@@ -93,6 +105,7 @@ float percentage = 0; // Percentage of pixels received
int saved_image_count = 0; // Stats - number of images processed
int bad_image_count = 0; // Stats - number of images that had lost pixels
String filename = "";
int drawLoopCount = 0; // Used for the fade out
@@ -126,42 +139,65 @@ void setup() {
}
void draw() {
if (mouseClick) buttonClicked();
switch(state) {
case 0: // Init varaibles, send start request
tint(0, 0, 0, 255);
println();
flushBuffer();
println("");
print("Ready: ");
if (running) {
tint(0, 0, 0, 255);
flushBuffer();
println("");
print("Ready: ");
xpos = 0;
ypos = 0;
serialCount = 0;
progress_bar = 0;
pixel_count = 0;
percentage = 0;
drawLoopCount = frameCount;
lastPixelTime = millis() + 1000;
xpos = 0;
ypos = 0;
serialCount = 0;
progress_bar = 0;
pixel_count = 0;
percentage = 0;
drawLoopCount = frameCount;
lastPixelTime = millis() + 1000;
state = 1;
state = 1;
} else {
if (millis() > beginTime) {
beginTime = millis() + 500;
dimmed = !dimmed;
if (dimmed) drawButton(buttonDimmed);
else drawButton(buttonStopped);
}
}
break;
case 1: // Console message, give server some time
print("requesting image ");
serial.write("S");
delay(10);
beginTime = millis() + 1000;
beginTime = millis();
requestTime = millis() + 1000;
requestCount = 1;
state = 2;
break;
case 2: // Get size and set start time for rendering duration report
if (millis() > beginTime) {
System.err.println(" - no response!");
state = 0;
if (millis() > requestTime) {
requestCount++;
print("*");
serial.clear();
serial.write("S");
if (requestCount > 32) {
requestCount = 0;
System.err.println(" - no response!");
state = 0;
}
requestTime = millis() + 1000;
}
if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel
getFilename();
flushBuffer(); // Precaution in case image header size increases in later versions
lastPixelTime = millis() + 1000;
beginTime = millis();
state = 3;
}
@@ -169,7 +205,7 @@ void draw() {
case 3: // Request pixels and render returned RGB values
state = renderPixels(); // State will change when all pixels are rendered
// Request more pixels, changing the number requested allows the average transfer rate to be controlled
// The pixel transfer rate is dependant on four things:
// 1. The frame rate defined in this Processing sketch in setup()
@@ -183,17 +219,17 @@ void draw() {
//serial.write("RRRR"); // 4 x NPIXELS more
//serial.write("RR"); // 2 x NPIXELS more
//serial.write("R"); // 1 x NPIXELS more
if (!running) state = 4;
break;
case 4: // Pixel receive time-out, flush serial buffer
println();
flushBuffer();
state = 6;
break;
case 5: // Save the image to the sketch folder (Ctrl+K to access)
saveScreenshot();
saved_image_count++;
saved_image_count++;
println("Saved image count = " + saved_image_count);
if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count);
drawLoopCount = frameCount; // Reset value ready for counting in step 6
@@ -232,13 +268,16 @@ void drawWindow()
textSize(20);
fill(0);
text("Bodmer's TFT image viewer", width/2, height-6);
if (running) drawButton(buttonRunning);
else drawButton(buttonStopped);
}
void flushBuffer()
{
//println("Clearing serial pipe after a time-out");
int clearTime = millis() + 50;
while ( millis() < clearTime ) serial.read();
while ( millis() < clearTime ) serial.clear();
}
boolean getSize()
@@ -274,6 +313,71 @@ boolean getSize()
return false;
}
void saveScreenshot()
{
println();
if (saved_image_count < max_allowed)
{
if (filename == "") filename = "tft_screen_" + (n++);
filename = filename + image_type;
println("Saving image as \"" + filename + "\"");
if (save_border)
{
PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);
partialSave.save(filename);
} else {
PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);
partialSave.save(filename);
}
if (n>=max_images) n = 0;
}
else
{
System.err.println(max_allowed + " saved image count exceeded, restart the sketch");
}
}
void getFilename()
{
int readTime = millis() + 20;
int inByte = 0;
filename = "";
while ( serial.available() > 0 && millis() < readTime && inByte != '.')
{
inByte = serial.read();
if (inByte == ' ') inByte = '_';
if ( unicodeCheck(inByte) ) filename += (char)inByte;
}
inByte = serial.read();
if (inByte == '@') filename += "_" + timeCode();
else if (inByte == '#') filename += "_" + saved_image_count%100;
else if (inByte == '%') filename += "_" + millis();
else if (inByte != '*') filename = "";
inByte = serial.read();
if (inByte == 'j') image_type =".jpg";
else if (inByte == 'b') image_type =".bmp";
else if (inByte == 'p') image_type =".png";
else if (inByte == 't') image_type =".tif";
}
boolean unicodeCheck(int unicode)
{
if ( unicode >= '0' && unicode <= '9' ) return true;
if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true;
if ( unicode == '_' || unicode == '/' ) return true;
return false;
}
String timeCode()
{
String timeCode = (int)year() + "_" + (int)month() + "_" + (int)day() + "_";
timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second();
return timeCode;
}
int renderPixels()
{
if ( serial.available() > 0 ) {
@@ -310,7 +414,7 @@ int renderPixels()
if (ypos>=tft_height) {
ypos = 0;
if ((int)percentage <100) {
while(progress_bar++ < 64) print(" ");
while (progress_bar++ < 64) print(" ");
percent(100);
}
println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s");
@@ -363,24 +467,6 @@ void percent(float percentage)
text(" [ " + (int)percentage + "% ]", 10, height-8);
}
void saveScreenshot()
{
println();
String filename = "tft_screen_" + n + image_type;
println("Saving image as \"" + filename);
if (save_border)
{
PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);
partialSave.save(filename);
} else {
PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);
partialSave.save(filename);
}
n = n + 1;
if (n>=max_images) n = 0;
}
boolean fadedImage()
{
int opacity = frameCount - drawLoopCount; // So we get increasing fade
@@ -398,4 +484,42 @@ boolean fadedImage()
return true;
}
return false;
}
void drawButton(color buttonColor)
{
stroke(0);
fill(buttonColor);
rect(500 - 100, 540 - 26, 80, 24);
textAlign(CENTER);
textSize(20);
fill(0);
if (running) text(" Pause ", 500 - 60, height-7);
else text(" Run ", 500 - 60, height-7);
}
void buttonClicked()
{
mouseClick = false;
if (running) {
running = false;
drawButton(buttonStopped);
System.err.println("");
System.err.println("Stopped - click 'Run' button: ");
//noStroke();
//fill(50);
//rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
beginTime = millis() + 500;
dimmed = false;
state = 4;
} else {
running = true;
drawButton(buttonRunning);
}
}
void mousePressed() {
if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) {
mouseClick = true;
}
}

View File

@@ -28,8 +28,8 @@
// option "Show Sketch Folder" or press Ctrl+K
// Created by: Bodmer 5/3/17
// Updated by: Bodmer 10/3/17
// Version: 0.06
// Updated by: Bodmer 12/3/17
// Version: 0.07
// MIT licence applies, all text above must be included in derivative works
@@ -53,7 +53,9 @@ boolean save_border = true; // Save the image with a border
int border = 5; // Border pixel width #
boolean fade = false; // Fade out image after saving #
// #
int max_images = 10; // Maximum of numbered saved images before over-writing files #
int max_images = 100; // Maximum of numbered file images before over-writing files #
// #
int max_allowed = 1000; // Maximum number of save images allowed before a restart #
// #
// # End of the values to change for a particular setup #
// ###########################################################################################
@@ -77,6 +79,13 @@ color bgcolor2 = color(77, 183, 187); // Arduino IDE style background color
// TFT image frame greyscale value (dark grey)
color frameColor = 42;
color buttonStopped = color(255, 0, 0);
color buttonRunning = color(128, 204, 206);
color buttonDimmed = color(180, 0, 0);
boolean dimmed = false;
boolean running = true;
boolean mouseClick = false;
int[] rgb = new int[3]; // Buffer for the colour bytes
int indexRed = 0; // Colour byte index in the array
int indexGreen = 1;
@@ -93,6 +102,9 @@ int beginTime = 0;
int pixelWaitTime = 1000; // Maximum 1000ms wait for image pixels to arrive
int lastPixelTime = 0; // Time that "image send" command was sent
int requestTime = 0;
int requestCount = 0;
int state = 0; // State machine current state
int progress_bar = 0; // Console progress bar dot count
@@ -101,6 +113,7 @@ float percentage = 0; // Percentage of pixels received
int saved_image_count = 0; // Stats - number of images processed
int bad_image_count = 0; // Stats - number of images that had lost pixels
String filename = "";
int drawLoopCount = 0; // Used for the fade out
@@ -134,42 +147,65 @@ void setup() {
}
void draw() {
if (mouseClick) buttonClicked();
switch(state) {
case 0: // Init varaibles, send start request
tint(0, 0, 0, 255);
println();
flushBuffer();
println("");
print("Ready: ");
if (running) {
tint(0, 0, 0, 255);
flushBuffer();
println("");
print("Ready: ");
xpos = 0;
ypos = 0;
serialCount = 0;
progress_bar = 0;
pixel_count = 0;
percentage = 0;
drawLoopCount = frameCount;
lastPixelTime = millis() + 1000;
xpos = 0;
ypos = 0;
serialCount = 0;
progress_bar = 0;
pixel_count = 0;
percentage = 0;
drawLoopCount = frameCount;
lastPixelTime = millis() + 1000;
state = 1;
state = 1;
} else {
if (millis() > beginTime) {
beginTime = millis() + 500;
dimmed = !dimmed;
if (dimmed) drawButton(buttonDimmed);
else drawButton(buttonStopped);
}
}
break;
case 1: // Console message, give server some time
print("requesting image ");
serial.write("S");
delay(10);
beginTime = millis() + 1000;
beginTime = millis();
requestTime = millis() + 1000;
requestCount = 1;
state = 2;
break;
case 2: // Get size and set start time for rendering duration report
if (millis() > beginTime) {
System.err.println(" - no response!");
state = 0;
if (millis() > requestTime) {
requestCount++;
print("*");
serial.clear();
serial.write("S");
if (requestCount > 32) {
requestCount = 0;
System.err.println(" - no response!");
state = 0;
}
requestTime = millis() + 1000;
}
if ( getSize() == true ) { // Go to next state when we have the size and bits per pixel
getFilename();
flushBuffer(); // Precaution in case image header size increases in later versions
lastPixelTime = millis() + 1000;
beginTime = millis();
state = 3;
}
@@ -177,7 +213,7 @@ void draw() {
case 3: // Request pixels and render returned RGB values
state = renderPixels(); // State will change when all pixels are rendered
// Request more pixels, changing the number requested allows the average transfer rate to be controlled
// The pixel transfer rate is dependant on four things:
// 1. The frame rate defined in this Processing sketch in setup()
@@ -191,17 +227,17 @@ void draw() {
//serial.write("RRRR"); // 4 x NPIXELS more
//serial.write("RR"); // 2 x NPIXELS more
//serial.write("R"); // 1 x NPIXELS more
if (!running) state = 4;
break;
case 4: // Pixel receive time-out, flush serial buffer
println();
flushBuffer();
state = 6;
break;
case 5: // Save the image to the sketch folder (Ctrl+K to access)
saveScreenshot();
saved_image_count++;
saved_image_count++;
println("Saved image count = " + saved_image_count);
if (bad_image_count > 0) System.err.println(" Bad image count = " + bad_image_count);
drawLoopCount = frameCount; // Reset value ready for counting in step 6
@@ -240,13 +276,16 @@ void drawWindow()
textSize(20);
fill(0);
text("Bodmer's TFT image viewer", width/2, height-6);
if (running) drawButton(buttonRunning);
else drawButton(buttonStopped);
}
void flushBuffer()
{
//println("Clearing serial pipe after a time-out");
int clearTime = millis() + 50;
while ( millis() < clearTime ) serial.read();
while ( millis() < clearTime ) serial.clear();
}
boolean getSize()
@@ -282,6 +321,71 @@ boolean getSize()
return false;
}
void saveScreenshot()
{
println();
if (saved_image_count < max_allowed)
{
if (filename == "") filename = "tft_screen_" + (n++);
filename = filename + image_type;
println("Saving image as \"" + filename + "\"");
if (save_border)
{
PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);
partialSave.save(filename);
} else {
PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);
partialSave.save(filename);
}
if (n>=max_images) n = 0;
}
else
{
System.err.println(max_allowed + " saved image count exceeded, restart the sketch");
}
}
void getFilename()
{
int readTime = millis() + 20;
int inByte = 0;
filename = "";
while ( serial.available() > 0 && millis() < readTime && inByte != '.')
{
inByte = serial.read();
if (inByte == ' ') inByte = '_';
if ( unicodeCheck(inByte) ) filename += (char)inByte;
}
inByte = serial.read();
if (inByte == '@') filename += "_" + timeCode();
else if (inByte == '#') filename += "_" + saved_image_count%100;
else if (inByte == '%') filename += "_" + millis();
else if (inByte != '*') filename = "";
inByte = serial.read();
if (inByte == 'j') image_type =".jpg";
else if (inByte == 'b') image_type =".bmp";
else if (inByte == 'p') image_type =".png";
else if (inByte == 't') image_type =".tif";
}
boolean unicodeCheck(int unicode)
{
if ( unicode >= '0' && unicode <= '9' ) return true;
if ( (unicode >= 'A' && unicode <= 'Z' ) || (unicode >= 'a' && unicode <= 'z')) return true;
if ( unicode == '_' || unicode == '/' ) return true;
return false;
}
String timeCode()
{
String timeCode = (int)year() + "_" + (int)month() + "_" + (int)day() + "_";
timeCode += (int)hour() + "_" + (int)minute() + "_" + (int)second();
return timeCode;
}
int renderPixels()
{
if ( serial.available() > 0 ) {
@@ -318,7 +422,7 @@ int renderPixels()
if (ypos>=tft_height) {
ypos = 0;
if ((int)percentage <100) {
while(progress_bar++ < 64) print(" ");
while (progress_bar++ < 64) print(" ");
percent(100);
}
println("Image fetch time = " + (millis()-beginTime)/1000.0 + " s");
@@ -371,24 +475,6 @@ void percent(float percentage)
text(" [ " + (int)percentage + "% ]", 10, height-8);
}
void saveScreenshot()
{
println();
String filename = "tft_screen_" + n + image_type;
println("Saving image as \"" + filename);
if (save_border)
{
PImage partialSave = get(x_offset - border, y_offset - border, tft_width + 2*border, tft_height + 2*border);
partialSave.save(filename);
} else {
PImage partialSave = get(x_offset, y_offset, tft_width, tft_height);
partialSave.save(filename);
}
n = n + 1;
if (n>=max_images) n = 0;
}
boolean fadedImage()
{
int opacity = frameCount - drawLoopCount; // So we get increasing fade
@@ -408,5 +494,42 @@ boolean fadedImage()
return false;
}
void drawButton(color buttonColor)
{
stroke(0);
fill(buttonColor);
rect(500 - 100, 540 - 26, 80, 24);
textAlign(CENTER);
textSize(20);
fill(0);
if (running) text(" Pause ", 500 - 60, height-7);
else text(" Run ", 500 - 60, height-7);
}
void buttonClicked()
{
mouseClick = false;
if (running) {
running = false;
drawButton(buttonStopped);
System.err.println("");
System.err.println("Stopped - click 'Run' button: ");
//noStroke();
//fill(50);
//rect( (width - tft_width)/2, y_offset, tft_width, tft_height);
beginTime = millis() + 500;
dimmed = false;
state = 4;
} else {
running = true;
drawButton(buttonRunning);
}
}
void mousePressed() {
if (mouseX > (500 - 100) && mouseX < (500 - 20) && mouseY > (540 - 26) && mouseY < (540 - 2)) {
mouseClick = true;
}
}
*/

View File

@@ -11,11 +11,13 @@
// Created by: Bodmer 27/1/17
// Updated by: Bodmer 10/3/17
// Version: 0.06
// Version: 0.07
// MIT licence applies, all text above must be included in derivative works
//====================================================================================
// Definitions
//====================================================================================
#define BAUD_RATE 250000 // Maximum Serial Monitor rate for other messages
#define DUMP_BAUD_RATE 921600 // Rate used for screen dumps
@@ -24,20 +26,46 @@
#define BITS_PER_PIXEL 16 // 24 for RGB colour format, 16 for 565 colour format
// File names must be alpha-numeric characters (0-9, a-z, A-Z) or "/" underscore "_"
// other ascii characters are stripped out by client, including / generates
// sub-directories
#define DEFAULT_FILENAME "tft_screenshots/screenshot" // In case none is specified
#define FILE_TYPE "png" // jpg, bmp, png, tif are valid
// Filename extension
// '#' = add 0-9, '@' = add timestamp, '%' add millis() timestamp, '*' = add nothing
// '@' and '%' will generate new unique filenames, so beware of cluttering up your
// hard drive with lots of images! The PC client sketch is set to limit the number of
// saved images to 1000 and will then prompt for a restart.
#define FILE_EXT '%'
// Number of pixels to send in a burst (minimum of 1), no benefit above 8
// NPIXELS values and render times: 1 = 5.0s, 2 = 1.75s, 4 = 1.68s, 8 = 1.67s
#define NPIXELS 8 // Must be integer division of both TFT width and TFT height
// Start a screen dump server (serial or network)
//====================================================================================
// Screen server call with no filename
//====================================================================================
// Start a screen dump server (serial or network) - no filename specified
boolean screenServer(void)
{
// With no filename the screenshot will be saved with a default name e.g. tft_screen_#.xxx
// where # is a number 0-9 and xxx is a file type specified below
return screenServer(DEFAULT_FILENAME);
}
//====================================================================================
// Screen server call with filename
//====================================================================================
// Start a screen dump server (serial or network) - filename specified
boolean screenServer(String filename)
{
Serial.end(); // Stop the serial port (clears buffers too)
Serial.begin(DUMP_BAUD_RATE); // Force baud rate to be high
delay(0); // Equivalent to yield() for ESP8266;
boolean result = serialScreenServer(); // Screenshot serial port server
//boolean result = wifiScreenServer(); // Screenshot WiFi UDP port server (WIP)
boolean result = serialScreenServer(filename); // Screenshot serial port server
//boolean result = wifiScreenServer(filename); // Screenshot WiFi UDP port server (WIP)
Serial.end(); // Stop the serial port (clears buffers too)
Serial.begin(BAUD_RATE); // Return baud rate to normal
@@ -50,10 +78,11 @@ boolean screenServer(void)
return result;
}
// Screenshot serial port server (Processing sketch acts as client)
boolean serialScreenServer(void)
//====================================================================================
// Serial server function that sends the data to the client
//====================================================================================
boolean serialScreenServer(String filename)
{
// Precautionary receive buffer garbage flush for 50ms
uint32_t clearTime = millis() + 50;
while ( millis() < clearTime && Serial.read() >= 0) delay(0); // Equivalent to yield() for ESP8266;
@@ -78,16 +107,8 @@ boolean serialScreenServer(void)
wait = false; // No need to wait anymore
lastCmdTime = millis(); // Set last received command time
// Send screen size using a simple header with delimiters for client checks
Serial.write('W');
Serial.write(tft.width() >> 8);
Serial.write(tft.width() & 0xFF);
Serial.write('H');
Serial.write(tft.height() >> 8);
Serial.write(tft.height() & 0xFF);
Serial.write('Y');
Serial.write(BITS_PER_PIXEL);
Serial.write('?');
// Send screen size etc using a simple header with delimiters for client checks
sendParameters(filename);
}
}
else
@@ -138,9 +159,36 @@ boolean serialScreenServer(void)
#endif
}
}
Serial.flush(); // Make sure all pixel bytes have been despatched
return true;
}
//====================================================================================
// Send screen size etc using a simple header with delimiters for client checks
//====================================================================================
void sendParameters(String filename)
{
Serial.write('W'); // Width
Serial.write(tft.width() >> 8);
Serial.write(tft.width() & 0xFF);
Serial.write('H'); // Height
Serial.write(tft.height() >> 8);
Serial.write(tft.height() & 0xFF);
Serial.write('Y'); // Bits per pixel (16 or 24)
Serial.write(BITS_PER_PIXEL);
Serial.write('?'); // Filename next
Serial.print(filename);
Serial.write('.'); // End of filename marker
Serial.write(FILE_EXT); // Filename extension identifier
Serial.write(*FILE_TYPE); // First character defines file type j,b,p,t
}