TTL カメラと Ethernet シールドで作るネットワークカメラ

ここでは、Arduino Uno R3 と Ethernet シールド、さらに JPEG TTL カメラ (VC0706) を組み合わせて、 ネットワークカメラを作成してみましょう。

カメラは Adafruit などで入手可能な VC0706 を使った TTL カメラを使いました。

静止画像はただちに JPEG 画像データとして取得できるので、自分で圧縮等を行う必要がありません。

Arduino にウェブサーバーを組み合わせることで、撮影した JPEG 画像データをブラウザでダウンロードできるようにします。

独自プロトコルで実装するのも軽くできたりしますが、特別なクライアントが必要になってしまいます。 その点、ウェブサーバを実装すればクライアントとしてブラウザが使えるので便利です。

概要は次の通り。単純ですね。

このネットワークでは DHCP が利用可能と想定しています。

Arduino + Ethernet シールドと VC0706 カメラモジュールの接続

Arduino Uno に Ethernet シールドを接続。TTL カメラの TX を D2 ピンへ、RX を D3 ピンへ接続。

ただし、 この TTL カメラは動作電源は 5V ですが、シリアル接続は 3.3V なので Arduino (TX) からカメラの (RX) へは 10kΩ の抵抗二つで分圧して、 2.5V としてカメラの RX に接続しています。

実際に接続した様子は次の通り。

画像データをダウンロードするためのウェブサーバ

Arduino ではウェブサーバを実装して、URL で /pic としたときに、写真を撮影し、JPEG データをダウンロードするようにしています。

Adafruit が提供している VC0706 ライブラリを使いました。

#include <Adafruit_VC0706.h>
#include <SoftwareSerial.h>   
#include <Ethernet.h>

byte mac[] = { 0xDE,0xAD,0xBE,0xEF,0xF0,0x0D };
EthernetServer server(80);
// CAMERA TX => 2
// CAMERA RX => 3
SoftwareSerial cameraconnection = SoftwareSerial(2, 3);
Adafruit_VC0706 cam = Adafruit_VC0706(&cameraconnection);
boolean camera_found = false;

void setup() {
  Serial.begin(9600);
  
  Ethernet.begin(mac);
  Serial.println(Ethernet.localIP());

  server.begin();

  if( cam.begin() ){
    camera_found = true;
  }
  else{
    return;
  }

}

void loop() {
    
  EthernetClient client = server.available();
  
  if( client ){

    Serial.println("*** New Client ***");
    
    boolean currentLineIsBlank = true;
    boolean requestLineRead = false;
    char reqLine[64];
    char *p = reqLine;
    char *method = NULL;
    char *uri = NULL;
    const char delims[2] = " ";
    memset( reqLine, 0, sizeof(reqLine) );
    
    while( client.connected() ){
      char c = client.read();
      //Serial.write( c );

      // Query
      if( !requestLineRead 
        && ( p - reqLine ) < (sizeof(reqLine) - 1) 
        && c != '\r' 
        && c != '\n' ){
        *p = c;
        p++;
      }
      
      if( c == '\n' && currentLineIsBlank ){

        // Parse Query
        p = strtok(reqLine, delims);
        while( p ){
          if( !method ){
            method = p;
          }
          else if( !uri ){
            uri = p;
          }
          p = strtok(NULL, delims);
        }

        // Send Response
        if( camera_found && !strcmp(method, "GET") && !strcmp(uri, "/pic") ){

          cam.setImageSize(VC0706_320x240);

          if( cam.takePicture() ){

            uint16_t jpglen = cam.frameLength();
            String s = "Content-Length: ";
            s += jpglen;
            
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: image/jpeg");
            client.println( s );
            client.println("Connection: close");
            client.println();

            byte wCount = 0;
            while (jpglen > 0) {
              uint8_t *buf;
              uint8_t bytesToRead = min(64, jpglen);
              buf = cam.readPicture(bytesToRead);

              client.write(buf, bytesToRead);
              
              jpglen -= bytesToRead;
            }

            cam.reset();
          }
          else {
            client.println("HTTP/1.1 500 Server Internal Error");
            client.println("Content-Type: text/plain");
            client.println("Connection: close");
            client.println();
            client.println("I'm sorry :(");
          }
          
        }
        else{
          client.println("HTTP/1.1 404 Not Found");
          client.println("Content-Type: text/plain");
          client.println("Connection: close");
          client.println();
          client.println("File Not Found");
        }
        
        break;
      }
      if( c == '\n' ){
        currentLineIsBlank = true;
        if( !requestLineRead ){
          requestLineRead = true;
        }
      }
      else if( c != '\r' ){
        currentLineIsBlank = false;
      }
    }
    
    delay( 10 );
    client.stop();
    
    Serial.println("Client disconnected");

  }

}

ウェブサーバ実装部分の補足説明については「Ethernet ライブラリを利用した単純な Web Server の実装」にも書いてます。

ページが完全に切り替わるのではなくて、画像をダウンロードするだけなら "Content-Disposition" で attachment を指定すれば OK のはずです。(ここでは試してませんが)

動作試験

家の LAN に接続して、スマホ (Android) 上のブラウザからリクエストします。 割り当てられた IP アドレスはシリアルモニタ側に出力されているはずです。

少々遅さが気になりますが、とりあえず目的は達しています。

実際の画像はこちらです。

ここでは 320x240 にしてますが、takePicture メソッドで写真撮影前に setImageSize メソッドでサイズ指定すれば 640x480 まで指定可能です。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 基礎からの IoT 入門