// | |
// WebCam project based on Spresense Hardware by Manuel Iglesias, Feb. 2019 | |
// | |
#include <SPI.h> | |
#include <Ethernet.h> | |
#include <Flash.h> | |
#include <SDHCI.h> | |
#include <TinyWebServer.h> | |
#include <WebSockets.h> | |
#include <WebSocketsClient.h> | |
#include <WebSocketsServer.h> | |
#include <Camera.h> | |
#define WIZSSIZE 4096 // 4 sockets | |
//#define WIZSSIZE 16384 | |
#define PIN_SPI_SS_ETHERNET_LIB 8 | |
// Enter a MAC address and IP address for your controller below. | |
// The IP address will be dependent on your local network: | |
byte mac[] = { | |
0x00, 0x08, 0xDC, 0x1D, 0x2C, 0x3D | |
}; | |
IPAddress ip(192, 168, 1, 145); | |
SDClass SD; | |
typedef struct { | |
const char* wb_name; | |
CAM_WHITE_BALANCE cam_wb; | |
} camera_wb_list; | |
camera_wb_list whitebalance_list[]{ | |
{"CAM_WHITE_BALANCE_INCANDESCENT", CAM_WHITE_BALANCE_INCANDESCENT}, | |
{"CAM_WHITE_BALANCE_FLUORESCENT", CAM_WHITE_BALANCE_FLUORESCENT}, | |
{"CAM_WHITE_BALANCE_DAYLIGHT", CAM_WHITE_BALANCE_DAYLIGHT}, | |
{"CAM_WHITE_BALANCE_FLASH", CAM_WHITE_BALANCE_FLASH}, | |
{"CAM_WHITE_BALANCE_CLOUDY", CAM_WHITE_BALANCE_CLOUDY}, | |
{"CAM_WHITE_BALANCE_SHADE", CAM_WHITE_BALANCE_SHADE}, | |
}; | |
CAM_WHITE_BALANCE camera_wb = CAM_WHITE_BALANCE_DAYLIGHT; | |
bool cam_cfg_change = false; | |
volatile bool camStreamRdy = false; | |
uint8_t * imageBuf = NULL; | |
size_t imageSize = 0; | |
int imageHeight = 0; | |
int imageWidth = 0; | |
unsigned char start_flag = 0xAA; | |
unsigned char end_flag = 0xFF; | |
unsigned char ip_flag = 0x11; | |
IPAddress localip; | |
#define WEBSOCK_PORT 9001 | |
WebSocketsServer webSocket(WEBSOCK_PORT); // create a websocket server on port 81 | |
void startWebSocket() { // Start a WebSocket server | |
webSocket.begin(); // start the websocket server | |
webSocket.onEvent(webSocketEvent); // if there's an incomming websocket message, go to function 'webSocketEvent' | |
Serial.printf("WebSocket server started at port %d\n\r", WEBSOCK_PORT); | |
} | |
boolean file_handler(TinyWebServer& web_server); | |
boolean index_handler(TinyWebServer& web_server); | |
boolean white_balance_handler(TinyWebServer& web_server, char* buffer, int size); | |
boolean has_filesystem = true; | |
File file; | |
TinyWebServer::PathHandler handlers[] = { | |
{"/variable" "*", TinyWebServer::GET, &TinyWebFormHandler::form_handler }, | |
{"/submitform" "*", TinyWebServer::POST, &TinyWebFormHandler::form_handler }, | |
{"/", TinyWebServer::GET, &index_handler }, | |
{"/" "*", TinyWebServer::GET, &file_handler }, | |
{NULL}, | |
}; | |
TinyWebFormHandler::FormList form_handler_list[] = { | |
{"available", NULL}, | |
{"whitebalance", &white_balance_handler}, | |
{"camera_value_get", &camera_value_get}, | |
{NULL}, | |
}; | |
// FORM INIT | |
// somehow this is hardwired | |
void init_form_keywords(void){ | |
TinyWebFormHandler::add_form_keyword("submitform"); | |
TinyWebFormHandler::add_form_keyword("variable"); | |
TinyWebFormHandler::list_keywords(); | |
} | |
// ********************* | |
// FORM HANDLERS | |
//********************** | |
// GET | |
boolean camera_value_get(TinyWebServer& web_server, char* buffer, int size) { | |
// Serial.println("GET camera_value_get"); | |
// Serial.println(web_server.get_path()); | |
// Send values ... | |
web_server.send_error_code(200); | |
web_server.send_content_type("text/plain"); | |
web_server.end_headers(); | |
String variables = "whitebalance="; | |
for(int i=0; i<sizeof(whitebalance_list); i++){ | |
if(camera_wb == whitebalance_list[i].cam_wb){ | |
variables += whitebalance_list[i].wb_name; | |
break; | |
} | |
} | |
web_server << F(variables.c_str()); | |
return true; | |
} | |
// POST | |
boolean white_balance_handler(TinyWebServer& web_server, char* buffer, int size){ | |
// Serial.println("FORM white_balance_handler"); | |
// Serial.println(web_server.get_path()); | |
char *whitebalance = TinyWebFormHandler::get_var_from_post_data(buffer, "whitebalance"); | |
// adjust white balance variable camera_wb | |
for(int i=0; i<sizeof(whitebalance_list); i++){ | |
if(!strcmp(whitebalance_list[i].wb_name, whitebalance)){ | |
camera_wb = whitebalance_list[i].cam_wb; | |
cam_cfg_change = true; | |
break; | |
} | |
} | |
free(whitebalance); | |
web_server.send_error_code(200); | |
web_server.end_headers(); | |
return true; | |
} | |
// ********************* | |
// END FORM HANDLERS | |
//********************** | |
boolean file_handler(TinyWebServer& web_server) { | |
if(!has_filesystem) { | |
web_server.send_error_code(500); | |
web_server << F("Internal Server Error"); | |
return true; | |
} | |
char* filename = TinyWebServer::get_file_from_path(web_server.get_path()); | |
if(!filename) { | |
web_server.send_error_code(400); | |
web_server << F("Bad Request"); | |
return true; | |
} | |
send_file_name(web_server, filename); | |
free(filename); | |
return true; | |
} | |
void send_file_name(TinyWebServer& web_server, const char* filename) { | |
TinyWebServer::MimeType mime_type | |
= TinyWebServer::get_mime_type_from_filename(filename); | |
file = SD.open(filename, FILE_READ); | |
if (file) { | |
web_server.send_error_code(200); | |
web_server.send_content_type(mime_type); | |
web_server.end_headers(); | |
Serial << F("Read file "); Serial.println(filename); | |
web_server.send_file(file); | |
file.close(); | |
} else { | |
web_server.send_error_code(404); | |
web_server.send_content_type("text/plain"); | |
web_server.end_headers(); | |
Serial << F("Could not find file: "); Serial.println(filename); | |
web_server << F("404 - File not found") << filename << "\n"; | |
} | |
} | |
boolean index_handler(TinyWebServer& web_server) { | |
web_server.send_error_code(200); | |
web_server.end_headers(); | |
web_server << F("<html><body><h1>Hello World Spresense SD!</h1></body></html>\n"); | |
return true; | |
} | |
const char* headers[] = { | |
"Content-Length", | |
NULL | |
}; | |
boolean has_ip_address = false; | |
TinyWebServer web = TinyWebServer(handlers, headers); | |
const char* ip_to_str(const uint8_t* ipAddr) | |
{ | |
static char buf[16]; | |
sprintf(buf, "%d.%d.%d.%d\0", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]); | |
return buf; | |
} | |
void listSDFile() { | |
Serial.println("Size\tFilename"); | |
Serial.println("----\t--------"); | |
File root = SD.open("/"); | |
listDirectory(root); | |
} | |
void listDirectory(File dir) { | |
while (true) { | |
File entry = dir.openNextFile(); | |
if (!entry) { | |
break; | |
} | |
if (entry.isDirectory()) { | |
listDirectory(entry); // recursive call | |
} else { | |
Serial.print(entry.size(), DEC); | |
Serial.print("\t"); | |
Serial.println(entry.name()); | |
} | |
entry.close(); | |
} | |
} | |
void webSocketEvent(uint8_t num, WStype_t typein, uint8_t * payload, size_t payloadlength) { // When a WebSocket message is received | |
int blk_count = 0; | |
char canvas_Q_VGA[] = "canvas-Q-VGA"; | |
char ipaddr[26]; | |
switch (typein) { | |
case WStype_DISCONNECTED: // if the websocket is disconnected | |
Serial.printf("[%d]", num); | |
Serial.print(" Disconnected!\n"); | |
break; | |
case WStype_CONNECTED: { // if a new websocket connection is established | |
delay(1000); | |
webSocket.sendBIN(num, &ip_flag, 1); | |
// Serial.printf("WS Connected [%d]", num); | |
IPAddress ip = webSocket.remoteIP(num); | |
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); | |
// parse local IP address | |
sprintf(ipaddr, "%d.%d.%d.%d", localip[0], localip[1], localip[2], localip[3]); | |
Serial.printf("Local IP of WS: %s\n\r", ipaddr); | |
webSocket.sendTXT(num, (const char *)ipaddr, strlen(ipaddr)); | |
} | |
break; | |
case WStype_TEXT: // if new text data is received | |
if (payloadlength == sizeof(canvas_Q_VGA)-1) { | |
if (memcmp(canvas_Q_VGA, payload, payloadlength) == 0) { | |
webSocket.sendBIN(num, &end_flag, 1); | |
} | |
} | |
if(cam_cfg_change == true){ // if configuration need to change | |
cam_cfg_change = false; | |
// change camera configuration | |
Serial.print("Adjusting new white balance value"); | |
theCamera.setAutoWhiteBalanceMode(camera_wb); | |
delay(1); | |
} | |
// capture image from camera stream | |
theCamera.startStreaming(true, CamCB); | |
for(int j=0; j < 1000; j++){ | |
delay(1); | |
if(camStreamRdy == true){ | |
break; | |
} | |
} | |
if (camStreamRdy == true) | |
{ | |
camStreamRdy = false; | |
theCamera.startStreaming(false, CamCB); | |
if(imageBuf != NULL){ | |
size_t len = imageSize/2; | |
blk_count = 2; | |
int j = 0; | |
for (int i=0; i<blk_count; i++) { | |
if (i == 0) { | |
webSocket.sendBIN(num, &start_flag, 1); | |
} | |
if (i == blk_count-1) { | |
webSocket.sendBIN(num, &end_flag, 1); | |
} | |
webSocket.sendBIN(num, &imageBuf[j], len); | |
j += len; | |
} | |
} //imageBuf != NULL | |
} | |
break; | |
case WStype_ERROR: // if new text data is received | |
Serial.println("Error"); | |
default: | |
Serial.print("WStype "); | |
Serial.print(typein); | |
Serial.println(" not handled \n"); | |
} | |
} | |
void setup() { | |
Serial.begin(115200); | |
Serial.println("Setting up SD card..."); | |
if (!SD.begin()) { | |
has_filesystem = false; | |
Serial.println("SD card is not present"); | |
} | |
Serial.println("Enter any character to Start USB MSC"); | |
bool startMSC = false; | |
int starttime = millis(); | |
while (!Serial.available()){ | |
if((millis() - starttime) > 3000){ | |
break; | |
} | |
} | |
if(Serial.available()){ | |
while (Serial.available()) { | |
Serial.read(); // dummy read to empty input buffer | |
} | |
startMSC = true; | |
} | |
if(startMSC){ | |
Serial.println("Starting USB MSC"); | |
// Start USB Mass Storage | |
if (SD.beginUsbMsc()) { | |
Serial.println("UsbMsc connect error"); | |
} | |
Serial.println("Finish USB MSC? (y/n)"); | |
while (true) { | |
while (!Serial.available()); | |
if(Serial.read() == 'y'){ | |
while (Serial.available()) { | |
Serial.read(); // dummy read to empty input buffer | |
} | |
break; | |
} | |
} | |
// Finish USB Mass Storage | |
if (SD.endUsbMsc()) { | |
Serial.println("UsbMsc disconnect error"); | |
} | |
Serial.println("<<< Finish USB Mass Storage Operation"); | |
delay(100); | |
} | |
// Display SD Card File List | |
listSDFile(); | |
init_form_keywords(); | |
TinyWebFormHandler::set_form_handler(form_handler_list); | |
/* begin() without parameters means that | |
* number of buffers = 1, 30FPS, QVGA, YUV 4:2:2 format */ | |
Serial.println("WebSockets Camera Demo"); | |
Serial.println("Prepare camera"); | |
theCamera.begin(1, | |
CAM_VIDEO_FPS_30, | |
CAM_IMGSIZE_QVGA_H, | |
CAM_IMGSIZE_QVGA_V, | |
CAM_IMAGE_PIX_FMT_YUV422); | |
/* Start video stream. | |
* If received video stream data from camera device, | |
* camera library call CamCB. | |
*/ | |
Serial.println("Streaming starts with New Websocket"); | |
theCamera.startStreaming(false, CamCB); | |
/* Auto white balance configuration */ | |
Serial.println("Set Auto white balance Default parameter"); | |
theCamera.setAutoWhiteBalanceMode(camera_wb); | |
/* Set parameters about still picture. | |
* In the following case, QUADVGA and JPEG. | |
*/ | |
Serial.printf("Setting up the Wiz550io Ethernet Module\n"); | |
Ethernet.init(8); | |
Ethernet.begin(mac); | |
// Ethernet.begin(mac, ip); | |
// Start the web server. | |
Serial.println("Web server starting..."); | |
web.begin(); | |
localip = Ethernet.localIP(); | |
Serial.print("Ready to accept HTTP requests at "); | |
Serial.println(localip); | |
startWebSocket(); | |
} | |
/** | |
* Callback from Camera library when video frame is captured. | |
*/ | |
void CamCB(CamImage img) | |
{ | |
/* Check the img instance is available or not. */ | |
if (img.isAvailable()) | |
{ | |
/* convert image data format to RGB565 */ | |
img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565); | |
// Set some image variables needed for Streaming process | |
imageBuf = img.getImgBuff(); | |
imageSize = img.getImgSize(); | |
imageHeight = img.getHeight(); | |
imageWidth = img.getWidth(); | |
// Signal a new Frame is Ready | |
camStreamRdy = true; | |
} | |
else | |
{ | |
Serial.print("Failed to get video stream image\n"); | |
} | |
} | |
void loop() { | |
webSocket.loop(); | |
web.process(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment