2014年10月30日木曜日

Arduino で遊ぼう(6) ネットワークカメラ

Pro Micro 3.3V + XBee WiFi + C1098-SS
でネットワークカメラになりました。

でも、RAMが辛いのかHTMLを少し長くしたりするとうまく動かなくなる。

カメラのライブラリのチューニングが必要かな。

とりあえず、今のスケッチ

〜〜〜〜〜〜

#include <SPI.h>
#include <CameraC1098.h>

/*
 XBee WiFi Connected Serial.
 XBee works in API Mode.

 Receive Serial and Parse API packet,
 then echo back by API packet.
 */
/**
 * 送信パケットの生成
 */
int makeXBeeApiSendIp(byte* pPacket, unsigned short u16Size, byte* pbIpAddrDst,
byte* pbPortDst, byte* pbPayload, word length) {
if (u16Size < length + 16) {
Serial.println("buffer too short");
return -1;
}

// パケット生成
pPacket[0] = 0x7E;
// length
u16Size = length + 12;
pPacket[1] = u16Size >> 8;
pPacket[2] = u16Size & 0x00FF;
// API Cmd
pPacket[3] = 0x20;
// FrameID
pPacket[4] = 0x00;
// IpAddrDst
pPacket[5] = pbIpAddrDst[0];
pPacket[6] = pbIpAddrDst[1];
pPacket[7] = pbIpAddrDst[2];
pPacket[8] = pbIpAddrDst[3];
// PortDst
pPacket[9] = pbPortDst[0];
pPacket[10] = pbPortDst[1];
// PortSrc
pPacket[11] = 0x26;
pPacket[12] = 0x16;
// Protocol
pPacket[13] = 0x01; //< TCP
// Transmit Options
// pPacket[14] = 0x00; //< Leave Socket open
pPacket[14] = 0x02; //< Terminate Socket after tx complete

// Payload
for (int i = 0; i < length; i++) {
pPacket[15 + i] = pbPayload[i];
}
// チェックサム計算
byte sum = 0;
for (int i = 0; i < length + 12; i++) {
sum += pPacket[3 + i];
}
pPacket[length + 15] = 0xFF - sum;

return length + 16;
}

/**
 * 受信パケットのダンプ
 */
void printPayload(byte* pPayload, word length) {
if (0 < length) {
// Serial.println("dump");
for (word i = 0; i < length; i++) {
// Serial.print(pPayload[i], HEX);
// Serial.print(",");
Serial.print((char) pPayload[i]);
}
// Serial.println();
}
}

/**
 * 受信パケットのパース
 */
byte g_pbIpAddrSrc[4];
byte g_pbPortSrc[2];
byte* g_pbPayload;
int parseXBeeApiReceiveIp(byte* pPacket, unsigned short u16Size,
byte* pbIpAddrSrc, byte* pbPortSrc, byte** ppbPayload) {
// APIコード確認
if (0xB0 != pPacket[3]) {
// Serial.print("wrong api code:");
// Serial.println(pPacket[3]);
return -1;
}
// 送信元IPアドレス取得
pbIpAddrSrc[0] = pPacket[4];
pbIpAddrSrc[1] = pPacket[5];
pbIpAddrSrc[2] = pPacket[6];
pbIpAddrSrc[3] = pPacket[7];
//        Serial.write(pbIpAddrSrc, 4);
//        Serial.println();
// 送信元ポート取得
pbPortSrc[0] = pPacket[10];
pbPortSrc[1] = pPacket[11];
//        Serial.write(pbPortSrc, 2);
//        Serial.println();
// ペイロード取得
*ppbPayload = &pPacket[14];

//        Serial.println(pPacket[1]);
//        Serial.println(pPacket[2]);

return ((int) pPacket[1] << 8) + (int) pPacket[2] - 11;
}

/**
 * 受信処理
 */
#define RECEIVE_IP 1
unsigned char u8ReceiveMode = 0;
#define RECEIVE_MODE_IDLE 0
#define RECEIVE_MODE_LEN1 1
#define RECEIVE_MODE_LEN2 2
#define RECEIVE_MODE_DATA 3
#define RECEIVE_MODE_CS 4
#define APIPACKET_SIZE_MAX 512
byte g_pu8ApiPacket[APIPACKET_SIZE_MAX];
unsigned short g_u16ApiPacketSize;
unsigned short g_u16ApiPacketLength;
int parseXBeeApiChar(byte data) {
// Serial.println(data, HEX);

switch (u8ReceiveMode) {
case RECEIVE_MODE_IDLE:
// wait for "0x7E"
if (0x7E == data) {
g_u16ApiPacketSize = 0;
g_u16ApiPacketLength = 0;
g_pu8ApiPacket[g_u16ApiPacketSize++] = data;
u8ReceiveMode = RECEIVE_MODE_LEN1;
// Serial.println("api start");
}
break;
case RECEIVE_MODE_LEN1:
g_pu8ApiPacket[g_u16ApiPacketSize++] = data;
u8ReceiveMode = RECEIVE_MODE_LEN2;
break;
case RECEIVE_MODE_LEN2:
g_pu8ApiPacket[g_u16ApiPacketSize++] = data;
u8ReceiveMode = RECEIVE_MODE_DATA;
g_u16ApiPacketLength = ((unsigned short) g_pu8ApiPacket[1] << 8)
+ (unsigned short) g_pu8ApiPacket[2];
// Serial.print("get len :");
// Serial.println(g_u16ApiPacketLength);
if (g_u16ApiPacketLength > (APIPACKET_SIZE_MAX - 4)) {
Serial.println("invalid data len");
u8ReceiveMode = RECEIVE_MODE_IDLE;
}
break;
case RECEIVE_MODE_DATA:
g_pu8ApiPacket[g_u16ApiPacketSize++] = data;
if ((g_u16ApiPacketSize - 3) >= g_u16ApiPacketLength) {
u8ReceiveMode = RECEIVE_MODE_CS;
// Serial.println("get data");
}
break;
case RECEIVE_MODE_CS:
g_pu8ApiPacket[g_u16ApiPacketSize++] = data;
u8ReceiveMode = RECEIVE_MODE_IDLE;
// Serial.println("get cs");
return RECEIVE_IP;
break;
default:
break;
}
return 0;
}

/**
 * SPI制御
 */
byte XBEE_SPI_ATTEN = 4;
byte XBEE_SPI_CS = 5;
/**
 * SPI初期化
 */
void spiInit() {
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV4); //< 8MHz/4=2MHz
SPI.begin();

// XBee SPI ATTEN
pinMode(XBEE_SPI_ATTEN, INPUT);

// XBee SPI CS
pinMode(XBEE_SPI_CS, OUTPUT);
digitalWrite(XBEE_SPI_CS, HIGH);

}

/**
 * SPI受信
 * @note 受信のみ
 */
int spiReceive() {
int len = 0;
if (LOW == digitalRead(XBEE_SPI_ATTEN)) {
digitalWrite(XBEE_SPI_CS, LOW);
if (RECEIVE_IP == parseXBeeApiChar(SPI.transfer(0x00))) {
len = parseXBeeApiReceiveIp(g_pu8ApiPacket, g_u16ApiPacketSize,
g_pbIpAddrSrc, g_pbPortSrc, &g_pbPayload);
Serial.print("[receive ip packet:");
Serial.print(len);
Serial.print("]");
Serial.println();
printPayload(g_pbPayload, len);
}

if (HIGH == digitalRead(XBEE_SPI_ATTEN)) {
digitalWrite(XBEE_SPI_CS, HIGH);
}
}

return len;
}

/**
 * SPI送信
 * TODO 送信中の受信処理
 */
void spiSend(byte* pData, word length) {
digitalWrite(XBEE_SPI_CS, LOW);
for (int i = 0; i < length; i++) {
SPI.transfer(pData[i]);
}
digitalWrite(XBEE_SPI_CS, HIGH);
}

static const char gsc_pcHtmlHeader[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"Content-Length: ";
#define USE_JAVASCRIPT
#ifdef USE_JAVASCRIPT
static const char gsc_pcHtmlContents1[] =
"<html>"
"<head>"
"<script type=\"text/javascript\">\r\n"
"myImage = new Image();\r\n"
"count = 0;\r\n"
"function loadImage(url) {\r\n"
"myImage.onload = function(){\r\n"
"loadImage(url);\r\n"
"document.getElementById(\"img_mine\").src = myImage.src;\r\n"
;
static const char gsc_pcHtmlContents2[] =
"}\r\n"
"myImage.src = url + \"?\" + count.toString();\r\n"
"count++;\r\n"
"}\r\n"
"</script>"
"</head>\r\n"
"<body>"
//"<h1>hello world</h1>"
//"message from Arduino<br/>"
"pro micro 3.3V<br/>"
//"with XBee WiFi<br/>"
;
static const char gsc_pcHtmlContents3[] =
//"connected by SPI<br>"
//"<address>su4yamamoto</address>"
"<br/>"
"<img id=\"img_mine\"/>\r\n"
"<script type=\"text/javascript\">\r\n"
"loadImage(\"img.jpg\");\r\n"
"</script>\r\n"
"</body>"
"</html>"
;
#else // #ifdef USE_JAVASCRIPT
static const char gsc_pcHtmlContents1[] =
"<html>"
"<head>"
"<meta http-equiv=\"Refresh\" content=\"5\">"
"</head>"
"<body>"
"<h1>hello world</h1>"
"message from Arduino<br/>"
"pro micro 3.3V<br/>"
"with XBee WiFi<br/>"
"connected by SPI<br>"
"<address>su4yamamoto</address>"
"<br/>"
"<img src='img.jpg'/>"
"</body>"
"</html>";
#endif // #ifdef USE_JAVASCRIPT
static const char gsc_pcHtmlHeaderJpeg[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: image/jpeg\r\n"
"Connection: close\r\n"
"Content-Length: ";
static const char gsc_pcHtmlLineFeed[] = "\r\n\r\n";
static const char gsc_pcHtmlNotFound[] = "HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 9\r\n"
"\r\n"
"Not Found\r\n";

CameraC1098 camera(&Serial1);
uint32_t pictureSizeCount = 0;
/**
 * This callback is called EVERY time a JPEG data packet is received.
 */
void getJPEGPicture_callback(uint32_t pictureSize, uint32_t packageSize,
uint16_t packageCount, byte* package) {
if (0 == pictureSizeCount) {
Serial.println();
Serial.print("jpeg size:");
Serial.println(pictureSize);
#ifndef HTML_HELLOWORLD
// first time -> send size info
char pcLength[16];
String strLength = String(pictureSize, DEC);
strLength.toCharArray(pcLength, sizeof(pcLength));
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc, (byte*) pcLength,
strLength.length()));
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc, (byte*) gsc_pcHtmlLineFeed,
sizeof(gsc_pcHtmlLineFeed) - 1));
#endif
}
// packageSize is the size of the picture part of the package
pictureSizeCount += packageSize;

// Serial.write(package, packageSize);
Serial.print(packageSize);
Serial.print(":");
Serial.println(pictureSizeCount);
#ifndef HTML_HELLOWORLD
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX, g_pbIpAddrSrc,
g_pbPortSrc, package, packageSize));
#endif//#ifdef HTML_HELLOWORLD
if (pictureSizeCount >= pictureSize) {
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc, (byte*) gsc_pcHtmlLineFeed,
sizeof(gsc_pcHtmlLineFeed) - 1));
Serial.flush();
Serial.println();
Serial.println("finished");
}
}
void getJpegImage() {

if (!camera.sync(CameraC1098::BAUD57600)) {
Serial.println("Sync failed");
return;
}

if (!camera.initial(CameraC1098::BAUD57600, CameraC1098::JR_320x240)) {
Serial.println("Initial failed");
return;
}
if (!camera.setPackageSize(64)) {
Serial.println("Package size failed.");
return;
}

if (!camera.snapshot()) {
Serial.println("Snapshot failed.");
return;
}

pictureSizeCount = 0;
if (!camera.getJPEGPicture(PROCESS_DELAY, &getJPEGPicture_callback)) {
Serial.println("Get JPEG failed.");
return;
}
}

byte toggle = 0;
void setup() {
// SPIの初期設定
spiInit();

Serial.begin(115200); //This pipes to the serial monitor
Serial1.begin(14400);
}
void loop() {
int len = spiReceive();

if (0 < len) {
// html reply
// Check Request
String strReq = String((char*) g_pbPayload);
                if (strReq.startsWith("GET /index.html")) {
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc,
(byte*) gsc_pcHtmlHeader,
sizeof(gsc_pcHtmlHeader) - 1));
char pcLength[16];
#ifdef USE_JAVASCRIPT
String strLength = String(
                                          sizeof(gsc_pcHtmlContents1) - 1
                                            + sizeof(gsc_pcHtmlContents2) - 1
                                            + sizeof(gsc_pcHtmlContents3) - 1
                                            , DEC);
#else //#ifdef USE_JAVASCRIPT
String strLength = String(sizeof(gsc_pcHtmlContents1) - 1, DEC);
#endif //#ifdef USE_JAVASCRIPT
strLength.toCharArray(pcLength, sizeof(pcLength));
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc, (byte*) pcLength,
strLength.length()));
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc, (byte*) gsc_pcHtmlLineFeed,
sizeof(gsc_pcHtmlLineFeed) - 1));
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc, (byte*) gsc_pcHtmlContents1,
sizeof(gsc_pcHtmlContents1) - 1));
#ifdef USE_JAVASCRIPT
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc, (byte*) gsc_pcHtmlContents2,
sizeof(gsc_pcHtmlContents2) - 1));
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc, (byte*) gsc_pcHtmlContents3,
sizeof(gsc_pcHtmlContents3) - 1));
#endif//#ifdef USE_JAVASCRIPT
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc, (byte*) gsc_pcHtmlLineFeed,
sizeof(gsc_pcHtmlLineFeed) - 1));
                }
else if (strReq.startsWith("GET /img.jpg")) {
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc,
(byte*) gsc_pcHtmlHeaderJpeg,
sizeof(gsc_pcHtmlHeaderJpeg) - 1));
getJpegImage();
} else {
Serial.println("invalid http request");
spiSend(g_pu8ApiPacket,
makeXBeeApiSendIp(g_pu8ApiPacket, APIPACKET_SIZE_MAX,
g_pbIpAddrSrc, g_pbPortSrc,
(byte*) gsc_pcHtmlNotFound,
sizeof(gsc_pcHtmlNotFound) - 1));
return;
}

}

#ifndef HTML_HELLOWORLD
if (0 < Serial.available()) {
while (-1 != Serial.read())
;
getJpegImage();
}
#endif
}

0 件のコメント:

コメントを投稿