How to control a motorized IP camera with Arduino to take pictures from different locations and post them online as if they came from different webcams? I’ll explain how to do it.

In this article I explain how I made a webcam control system using the famous Arduino microcontroller with ethernet module and Apexis APM-J902-Z-WS PTZ motorized camera.

Ingrediants:

  • a panoramic position
  • the Arduino microcontroller personally I recommend Arduino Mega
  • the Arduino Ethernet Shield ethernet module
  • a micro SD
  • a motorized webcam
  • a pc with the development tool for Arduino Arduino IDE
  • a usb cable to communicate with the arduino board
  • an internet connection

The goal: multiple webcams in one

Having a terrace placed in a very wide panoramic position, framing a single point was really a waste. I said to myself, why not take advantage of the motion capability of a motorized webcam to take pictures from different angles, and then post each photo on webcam sites, as if they were webcams physically placed in different places?

To do this, the webcam software was not enough. The idea of using Arduino seemed to me the simplest thing … cheaper and also fun!

Now I’ll explain how I did it.

Factual status analysis: what can the webcam do on its own?

webcam Apexis APM-J902-Z-WS

This is the Apexis APM-J902-Z-WS webcam. Let’s start from the state of affairs of what it can do.

First of all, the apexis APM-J902 is a wireless network webcam so it can be remotely controlled via the network (cable or wireless). The first thing to do is therefore to assign them a static IP address and configure it on the home switch or router. Via the network, it can be configured using the appropriate control panel.

Pannello di controllo della webcam apexis APM-J902-Z-WS

So let’s see what the cam can do for our project: the cam can receive and execute commands via http to move the lens right, left, down and up

  1. Receive and execute commands via http to return the image pointed to by the target at that moment
  2. Receive and execute commands via http to memorize up to 8 target positions
  3. Receive and execute commands via http to move the object to one of the 8 stored positions

Taking advantage of the above points it is easy to understand how this can be achieved. Just create a program that for each of the positions to be photographed runs in sequence:

  1. Goal positioning on position 1
  2. Image downloads on location 2
  3. Objective positioning on position 2
  4. Image downloads on location 2
  5. Objective positioning on position 2
  6. Image downloads on location 3
  7. Upload images to FTP server
  8. Wait 10 minutes (or as long as we want) and then repeat the entire cycle

Now just make a program that accomplishes all of this. The problem is that you should leave a pc on all day and all night just to do this !! That’s why I got the idea of using Arduino, which instead of a few dozen Watts, consumes about 4-6 Watts.

Assuming that the IP address of the cam is 10.0.0.100 the commands to be sent to the cam are simple url, and are the following.

Command to receive the “snapshot” image:

http://10.0.0.100:81/snapshot.cgi?user=NOMEUTENTE&pwd=PASSWORD

Command to recall the shift of the lens to a specific position:

http://10.0.0.100:81/decoder_control.cgi?command=31&user=NOMEUTENTE&pwd=PASSWORD

Solution: use Arduino + Ethernet Shield

TWO WORDS ABOUT ARDUINO:
Arduino (official sitre https://www.arduino.cc) is a programmable microcontroller, an all-Italian Open Source project. In a nutshell it is an electronic board equipped with a microprocessor, a memory to contain programs, various digital and analog inputs and outputs, a C / C ++ programming interface and a myriad of interfaceable components. Arduino is a low-cost platform and is currently used both by students for experimental projects, by hobbyists who are passionate about electronics, and for more professional uses.

There are several versions of Arduino. In my project I used Arduino Mega 2560 R3: Arduino Mega 2560 R3:

Aarduino Mega 2560-r3

Arduino is cspable to connect to the network, and therefore also to the internet through an additional board called Ethernet Shield:

Arduino Ethernet Shield - Arduino Modulo Ethernet

The two boards are used by superimposing the Ethernet Shield module on the Arduino board so that the connecting combs on the Ethernet board fit into the connector holes on the Arduino board.

We will use the integrated Micro SD memory reader of the Ethernet Shield module to store the captured images before sending them to the FTP server.

Using the appropiate development IDE and the appropriate libraries it is possible to program Arduino. In this article I will not explain how to install and configure the development tool on your pc as it is all well explained here.

The final result:

First of all let’s see the final result

These are the three images captured:

The three images are actually clean without the time and logo, which is then put by my PHP code whose operation is not covered here. But the end result is still this, that is, three distinct images as if they came from three different fixed webcams.

The software

Since the code is a bit long, I will not explain everything step by step but only some important details, leaving the explanation of the rest to the comments in the code.

The libraries we will need are the following:

#include <SPI.h>; // per la comunicazine seriale

Let’s move on to the declaration of the main variables. In pLet’s move on to the declaration of the main variables. In particular, the MAC ADDRESS of the Ethernet Shield card must be indicated. You get this number from a label on the card itself. It is essential to get the IP from the DHCP Client.

The setup () function starts when you power up the board and has the task of:

  1. Initialize the SD micro sim
  2. Initialize the serial communication with the PC for printing the logs
void setup()
{
    pinMode(LED_PIN, OUTPUT); // Led che indica se è in ciclo o in attesa 

   // initialize serial communications at 9600 bps:
  Serial.begin(9600);
  Serial.println("(c) Copyright 2013 Alessandro Scola");
  Serial.println("http://www.alessandroscola.it");
  Serial.println("Released under Apache License, version 2.0");
  Serial.println("Software da utilizzare con Arduino Mega 2560 + Ethernet Shield");
  Serial.println("Il software controlla una IP cam Apexis, muovendola in 3 posizioni \"preset\" differenti");
  Serial.println("salvando le 3 immagini su SD card,");
  Serial.println("e successivamente facendo l'upload delle immagini su Server remoto via FTP.");
  Serial.println("Il ciclo si ripete ogni 10 minuti. Ogni 10 minuti vengono acquisite le 3 immagini");
  Serial.println("********************************************");
  Serial.print("Controllo SD Card...");

  //OBBLIGATORIO per l'uso della SD. Dato che arduino comunica alla scheda SD ed alla scheda ethernet con gli stessi pin,
  //solo una delle due può essere utilizzata alla volta. Tramite il pin 53 impostato ad HIGH si disattiva la scheda ethernet
  //in modo da poter usare la scheda SD e viceversa. impostando il pin 53 a OUTPUT si fa in modo che la libreria SD abbia la capacità
  //di gestire in automatico l'attivazione e la disattivazione di questa (in modo da non doverlo fare noi ogni volta) ma questa
  //gestione automatica del pin 53 da parte della libreria SD inizia solo dopo aver inizializzato la libreria con il comando SD.begin(4)

   digitalWrite(53,HIGH);  // il pin 53 è solo per la scheda Arudino Mega. per le altre schede usare il pin 10
   pinMode(53,OUTPUT);     // il pin 53 è solo per la scheda Arudino Mega. per le altre schede usare il pin 10 

   // set up SD
   if(SD.begin(4) == 0)
      Serial.println("errore, non riesco ad accedere a SD");
   else
   {
     Serial.println("SD Card OK !");
   }
}

Fate attenzione al Pin 53 (per la Scheda Arduino Mega, 10 per le altre schede), deve essere impostato subito come uscita e subito a valore HIGH. Questo è spiegato nei commenti e serve a disabilitare la shceda ethernet per poter inizializzare la comunicazione con la microsim SD, dato che l’accesso alla SD è gestita dalla stessa scheda che gestisce la scheda di rete.

Il resto del codice C++ comprende la funzione loop() che rappresenta l’intero ciclco del programma e alcune funzioni per la gestione della connessione FTP.

void loop()
{
  int err =0;
  EthernetClient c;
  HttpClient http(c);
  long bufferCount = 0;

  digitalWrite(LED_PIN,HIGH);

   Serial.println("********************************************");
   Serial.println("Sto ottendendo indirizzo IP dal router ...");
   while (Ethernet.begin(mac) != 1)
   {
    Serial.print("Errore ottendendo indirizzo IP via DHCP, provo ancora ...");
    delay(15000);
   }
   Serial.println("Ok !");

 //******************************************************************************************

 //Da il comando per la rotazione in posizione 1
 // go tp POSIZIONE 1:
 Serial.println("********************************************");
 Serial.println ("Muovo la cam in POSIZIONE 1 e attendo che si posizioni");

 char kPathPOSIZIONE1[] ="/decoder_control.cgi?command=31&pwd=PASSWORD";
 err = http.get(kHostname, 81, kPathPOSIZIONE1);
 if (err != 0)
 {
   Serial.println ("Errore muovendo a POSIZIONE 1.");
 }
 http.stop();
 delay(8000); // attendo 8 secondi che la cam si posizioni

 //******************************************************************************************

  char kPath[] ="/snapshot.cgi?user=NOMEUTENTE&pwd=PASSWORD";
  err = http.get(kHostname, 81, kPath);
  if (err == 0)
  {
    Serial.println("Connessione con la cam OK !");

    err = http.responseStatusCode();
    if (err >= 0)
    {
      Serial.print("Codice risposta http: ");
      Serial.println(err);

      // Usually you'd check that the response code is 200 or a
      // similar "success" code (200-299) before carrying on,
      // but we'll print out whatever response we get

      err = http.skipResponseHeaders();
      if (err >= 0)
      {
        int bodyLen = http.contentLength();
        Serial.print("La risposta e' lunga: ");
        Serial.print(bodyLen);
        Serial.println(" bytes");

        file = SD.open("cam1.jpg", FILE_WRITE);
        file.seek(0);
        if (file) Serial.print("Salvo l'immagine su SD...");
        else Serial.println("Impossibile aprire file!");

        // Now we've got to the body, so we can print it out
        unsigned long timeoutStart = millis();
        byte c;
        // Whilst we haven't timed out haven't reached the end of the body

        bufferCount=0;
        while ( (http.connected() || http.available()) 
               ((millis() - timeoutStart) < kNetworkTimeout) ) { if (http.available()) { //c = http.read(); clientBuf[bufferCount] = http.read(); bufferCount++; // Print out this character //Serial.print(c); //if (file) file.write(c); if(bufferCount > 2047)
                  {
                    file.write(clientBuf,2048);
                    bufferCount = 0;
                  }

                // We read something, reset the timeout counter
                timeoutStart = millis();
            }
            else
            {
                // We haven't got any data, so let's pause to allow some to
                // arrive
                delay(kNetworkDelay);
            }
        }// fine while
        if(bufferCount > 0) file.write(clientBuf,bufferCount);

        file.close();
        Serial.println ("File chiuso.");
      }
      else
      {
        Serial.print("Failed to skip response headers: ");
        Serial.println(err);
      }
    }
    else
    {
      Serial.print("Nessuna risposta: ");
      Serial.println(err);
    }
  }
  else
  {
    Serial.print("Connect failed: ");
    Serial.println(err);
  }
  http.stop();

 //****************************************************************************************

 //Da il comando per la rotazione in posizione 2
 // go tp POSIZIONE 2:  

 Serial.println("********************************************");
 Serial.println ("Muovo la cam in POSIZIONE 2 e attendo che si posizioni");

 char kPathPOSIZIONE2[] ="/decoder_control.cgi?command=33&user=NOMEUTENTE&pwd=PASSWORD";
 err = http.get(kHostname, 81, kPathPOSIZIONE2);
 if (err != 0)
 {
   Serial.println ("Errore muovendo a POSIZIONE 2.");
 }
 http.stop();
 delay(6000);

 //******************************************************************************************

 // SALVA IMG 2

  err = http.get(kHostname, 81, kPath);
  if (err == 0)
  {
    Serial.println("Connessione con la cam OK !");

    err = http.responseStatusCode();
    if (err >= 0)
    {
      Serial.print("Codice risposta http: ");
      Serial.println(err);

      // Usually you'd check that the response code is 200 or a
      // similar "success" code (200-299) before carrying on,
      // but we'll print out whatever response we get

      err = http.skipResponseHeaders();
      if (err >= 0)
      {
        int bodyLen = http.contentLength();
        Serial.print("La risposta e' lunga: ");
        Serial.print(bodyLen);
        Serial.println(" bytes");

        file = SD.open("cam2.jpg", FILE_WRITE);
        file.seek(0);
        if (file) Serial.print("Salvo l'immagine su SD...");
        else Serial.println("Impossibile aprire file!");

        // Now we've got to the body, so we can print it out
        unsigned long timeoutStart = millis();
        byte c;
        // Whilst we haven't timed out haven't reached the end of the body
        bufferCount=0;
        while ( (http.connected() || http.available()) 
               ((millis() - timeoutStart) < kNetworkTimeout) ) { if (http.available()) { //c = http.read(); clientBuf[bufferCount] = http.read(); bufferCount++; // Print out this character //Serial.print(c); //if (file) file.write(c); if(bufferCount > 2047)
                  {
                    file.write(clientBuf,2048);
                    bufferCount = 0;
                  }

                // We read something, reset the timeout counter
                timeoutStart = millis();
            }
            else
            {
                // We haven't got any data, so let's pause to allow some to
                // arrive
                delay(kNetworkDelay);
            }
        }// fine while

        if(bufferCount > 0) file.write(clientBuf,bufferCount);
        file.close();
        Serial.println ("File chiuso.");
      }
      else
      {
        Serial.print("Failed to skip response headers: ");
        Serial.println(err);
      }
    }
    else
    {
      Serial.print("Nessuna risposta: ");
      Serial.println(err);
    }
  }
  else
  {
    Serial.print("Connect failed: ");
    Serial.println(err);
  }
  http.stop();

  //****************************************************************************************

 //Da il comando per la rotazione in posizione 3
 // go tp POSIZIONE 3:  

 Serial.println("********************************************");
 Serial.println ("Muovo la cam in POSIZIONE 3 e attendo che si posizioni");

 char kPathPOSIZIONE3[] ="/decoder_control.cgi?command=35&user=NOMEUTENTE&pwd=PASSWORD";
 err = http.get(kHostname, 81, kPathPOSIZIONE3);
 if (err != 0)
 {
   Serial.println ("Errore muovendo a POSIZIONE 3.");
 }
 http.stop();
 delay(4000);

 //******************************************************************************************

 // SALVA IMG 3

  err = http.get(kHostname, 81, kPath);
  if (err == 0)
  {
    Serial.println("Connessione con la cam OK !");

    err = http.responseStatusCode();
    if (err >= 0)
    {
      Serial.print("Codice risposta http: ");
      Serial.println(err);

      // Usually you'd check that the response code is 200 or a
      // similar "success" code (200-299) before carrying on,
      // but we'll print out whatever response we get

      err = http.skipResponseHeaders();
      if (err >= 0)
      {
        int bodyLen = http.contentLength();
        Serial.print("La risposta e' lunga: ");
        Serial.print(bodyLen);
        Serial.println(" bytes");
        Serial.println("Body returned follows:");

        file = SD.open("cam3.jpg", FILE_WRITE);
        file.seek(0);
        if (file) Serial.print("Salvo l'immagine  su SD...");
        else Serial.println("Impossibile aprire file!");

        // Now we've got to the body, so we can print it out
        unsigned long timeoutStart = millis();
        byte c;
        // Whilst we haven't timed out haven't reached the end of the body
        bufferCount=0;
        while ( (http.connected() || http.available()) 
               ((millis() - timeoutStart) < kNetworkTimeout) ) { if (http.available()) { //c = http.read(); clientBuf[bufferCount] = http.read(); bufferCount++; // Print out this character //Serial.print(c); //if (file) file.write(c); if(bufferCount > 2047)
                  {
                    file.write(clientBuf,2048);
                    bufferCount = 0;
                  }

                // We read something, reset the timeout counter
                timeoutStart = millis();
            }
            else
            {
                // We haven't got any data, so let's pause to allow some to
                // arrive
                delay(kNetworkDelay);
            }
        }// fine while
        if(bufferCount > 0) file.write(clientBuf,bufferCount);
        file.close();
        Serial.println ("File chiuso.");
      }
      else
      {
        Serial.print("Failed to skip response headers: ");
        Serial.println(err);
      }
    }
    else
    {
      Serial.print("Nessuna risposta: ");
      Serial.println(err);
    }
  }
  else
  {
    Serial.print("Connessione fallita: ");
    Serial.println(err);
  }
  http.stop();

  //******************************************************************************************

 //Da il comando per la rotazione in posizione 1
 // go tp POSIZIONE 1:
 Serial.println("********************************************");
 Serial.println ("Riporto la cam in POSIZIONE 1 e attendo che si posizioni");

 char kPathPOSIZIONE4[] ="/decoder_control.cgi?command=31&user=NOMEUTENTE&pwd=PASSWORD";
 err = http.get(kHostname, 81, kPathPOSIZIONE1);
 if (err != 0)
 {
   Serial.println ("Errore muovendo a POSIZIONE 1.");
 }
 http.stop();
 delay(8000); // attendo 8 secondi che la cam si posizioni

 //******************************************************************************************

  //Carico le immagini sul server FTP
  Serial.println("********************************************");
  Serial.println ("");
  Serial.println ("CARICO LE 3 IMMAGINI SU SERVER FTP");
  Serial.println ("");
  uploadFTP();
  Serial.println("********************************************");

  Serial.println("Attendo 10 minuti e poi ricomincero' il ciclo...");
  digitalWrite(LED_PIN, LOW);
  delay(600000);
  //Serial.println("Attendo 15 secondi ...");
  //delay(15000);
} // fine void loop()

// #################### FUNZIONI PER L'FTP: #########################
byte uploadFTP()
{
  char fileName1[13] = "CAM1.JPG";
  char fileName2[13] = "CAM2.JPG";
  char fileName3[13] = "CAM3.JPG";
  byte clientBuf[64];
  long clientCount = 0;

  HttpClient http(client);

  if (client.connect(server,21))
  {
    Serial.println(F("Connesso al server..."));
  }
  else
  {
    Serial.println(F("Connessione al server fallita."));
    return 0;
  }

   if(!eRcv()) return 0;

  client.println(F("NOMEUTENTE FTP"));

  if(!eRcv()) return 0;

  client.println(F("PASSWORD FTP"));

  if(!eRcv()) return 0;

  client.println(F("SYST"));

  if(!eRcv()) return 0;

  client.println(F("PASV"));

  if(!eRcv()) return 0;

  char *tStr = strtok(outBuf,"(,");
  int array_pasv[6];
  for ( int i = 0; i < 6; i++) {
    tStr = strtok(NULL,"(,");
    array_pasv[i] = atoi(tStr);
    if(tStr == NULL)
    {
      Serial.println(F("Bad PASV Answer"));    

    }
  }

  unsigned int hiPort,loPort;

  hiPort = array_pasv[4] < 8; loPort = array_pasv[5]  255; Serial.print(F("Data port: ")); hiPort = hiPort | loPort; Serial.println(hiPort); if (dclient.connect(server,hiPort)) { Serial.println(F("Data connected")); } else { Serial.println(F("Data connection failed")); client.stop(); return 0; } //******************************************************** client.println(F("TYPE I")); // Importante! seleziona modo BINARIO per il trasferimento !! client.println(F("CWD /www.alessandroscola.it/webcam-belluno/CAM1")); client.print(F("STOR ")); client.println(fileName1); if(!eRcv()) { dclient.stop(); return 0; } file = SD.open(fileName1, FILE_READ); if(!file) { Serial.println("Apertura SD fallita !"); return 0; } Serial.println(F("Writing")); clientCount=0; while(file.available()) { clientBuf[clientCount] = file.read(); clientCount++; if(clientCount > 63)
    {
      dclient.write(clientBuf,64);
      clientCount = 0;
    }
  }
  if(clientCount > 0) dclient.write(clientBuf,64);
  dclient.stop();
  Serial.println(F("Data disconnected"));
  file.close();

  //*********************************************************
  if (dclient.connect(server,hiPort))
  {
    Serial.println(F("Data connected"));
  }
  else
  {
    Serial.println(F("Data connection failed"));
    client.stop();
    return 0;
  }

   client.println(F("CWD /www.alessandroscola.it/webcam-belluno/CAM2"));
  client.print(F("STOR "));
  client.println(fileName2);

   if(!eRcv())
  {
    dclient.stop();
    return 0;
  }
  file = SD.open(fileName2, FILE_READ);
  if(!file)
  {
    Serial.println("Apertura SD fallita");
    return 0;
  }
  Serial.println(F("Writing"));
  clientCount=0;
  while(file.available())
  {
    clientBuf[clientCount] = file.read();
    clientCount++;

    if(clientCount > 63)
    {
      dclient.write(clientBuf,64);
      clientCount = 0;
    }
  }
  if(clientCount > 0) dclient.write(clientBuf,clientCount);
  dclient.stop();
  Serial.println(F("Data disconnected"));
  file.close();

  //*********************************************************
  if (dclient.connect(server,hiPort))
  {
    Serial.println(F("Data connected"));
  }
  else
  {
    Serial.println(F("Data connection failed"));
    client.stop();
    return 0;
  }

  client.println(F("CWD /www.alessandroscola.it/webcam-belluno/CAM3"));
  client.print(F("STOR "));
  client.println(fileName3);

   if(!eRcv())
  {
    dclient.stop();
    return 0;
  }

  file = SD.open(fileName3, FILE_READ);
  if(!file)
  {
    Serial.println("Apertura SD fallita");
    return 0;
  }
  Serial.println(F("Writing"));
  clientCount=0;
  while(file.available())
  {
    clientBuf[clientCount] = file.read();
    clientCount++;

    if(clientCount > 63)
    {
      dclient.write(clientBuf,64);
      clientCount = 0;
    }
  }
  if(clientCount > 0) dclient.write(clientBuf,clientCount);
  dclient.stop();
  Serial.println(F("Data disconnected"));
  file.close();
  //******************************************************
  dclient.stop();
  Serial.println(F("Data disconnected"));

  if(!eRcv()) return 0;

  client.println(F("QUIT"));

  if(!eRcv()) return 0;

  client.stop();
  Serial.println(F("Command disconnected"));

  file.close();
  Serial.println(F("SD Chiusa."));
  return 1;

} // fine doFTP()

byte eRcv()
{
  byte respCode;
  byte thisByte;

  while(!client.available()) delay(1);

  respCode = client.peek();

  outCount = 0;

  while(client.available())
  {
    thisByte = client.read();
    Serial.write(thisByte);

    if(outCount < 127) { outBuf[outCount] = thisByte; outCount++; outBuf[outCount] = 0; } } if(respCode >= '4')
  {
    efail();
    return 0;
  }

  return 1;
}

void efail()
{
  byte thisByte = 0;

  client.println(F("QUIT"));

  while(!client.available()) delay(1);

  while(client.available())
  {
    thisByte = client.read();
    Serial.write(thisByte);
  }

  client.stop();
  Serial.println(F("Command disconnected"));
  file.close();
  Serial.println(F("SD Chiusa."));
}

Help me support this blog

If you want you can help me concretely to support this blog. You can do it with a free donation, by credit card or paypal. I will be grateful to you.