Ralph Yamamoto
Published © MIT

Portable Solar Powered Surveillance System

A portable pan/tilt camera unit with PIR trigger and solar battery power.

AdvancedShowcase (no instructions)Over 1 day2,133

Things used in this project

Hardware components

Spresense boards (main & extension)
Sony Spresense boards (main & extension)
×1
Sony Spresense Camera board
×1
Solar Panel, 3.5W @ 18V
×1
USB Output Step Down Converter
×1
RPi UPS Battery Pack
×1
LCD Display 1.8 inch
×1
SparkFun Pan/Tilt Bracket Kit
×1
PIR Motion Sensor (generic)
PIR Motion Sensor (generic)
×1
ESP8266 ESP-01
Espressif ESP8266 ESP-01
×1
MicroSD Module (Generic)
×1
Molex FFC Cable 20p 6"
×1
USB-A to Right angle Micro USB Cable
×1
USB-A to Micro-USB Cable
USB-A to Micro-USB Cable
×1
Acrylic vase 6 inch diameter
×1
PVC pipe 6 inch diameter x 2 feet length
×1

Software apps and online services

Arduino IDE
Arduino IDE
OpenScad
ThingSpeak API
ThingSpeak API
Maker service
IFTTT Maker service
Pushbullet

Hand tools and fabrication machines

Velleman K8200 3D Printer

Story

Read more

Schematics

Portable Surveillance System Schematic

Schematic Diagram of hardware assembly

Code

Portable Surveillance System Code

C/C++
Arduino IDE Compatible Code
/*
 * @portable_surveillance_system.ino
 * @author Ralph Yamamoto
 * @Hackster "Make it Better with Sony" Contest program entry
 * @Hardware details: 
 *  Spresense main board
 *  SPresense extension board
 *  SPresense camera
 *  ESP8266-01 for WiFi
 *  LCD Display, 1.8", 160x128, TC7735, SPI
 *  Pan/tilt using 2 SG90 micro servos
 *  
 *  Using Spresense GPS and Camera interfaces.  
 * 
 *  January 27, 2019  Created
 *  
 *  This example code is in the public domain.
 */

/* include the libraries */
#include <SDHCI.h>
#include <stdio.h>  /* for sprintf */
#include <Camera.h>
#include <GNSS.h>
#include <TFT.h>
#include <Servo.h>

/* wifi parameters */
#define SSID        "removed"              // Must be changed - WiFi SSID/Hostname
#define PASS        "removed"              // Must be changed - Encrypted password
#define APIKEY      "removed"              // Must be changed - API Key ThingSpeak Channel
#define APIKEY2     "removed"              // Must be changed - API Key IFTTT Maker Channel
#define HOST        "api.thingspeak.com"   // Address of ThingSpeak API
#define HOST2       "maker.ifttt.com"      // Address of IFTTT Maker Channel API
#define PORT        "80"                   // ThingSpeak Port #
#define MONITOR     true                   // Debug messages on serial monitor
#define WIFIMONITOR false                  // WiFi activity on serial monitor
#define BROADCAST   true                   // Broadcast readings to ThingSpeak
#define POST_PERIOD 60000                  // 1 minutes = 60,000ms = 60sec


#define STRING_BUFFER_SIZE  128       /* %Buffer size */

static SpGnss Gnss;                   /* SpGnss object */
static Servo p_servo;                 /* Servo object Pan*/
static Servo t_servo;                 /* Servo object Tilt*/
static SpNavData NavData;             /* SpNavData object */


#define cs PIN_D10
#define dc PIN_D09
#define rst PIN_D08
TFT TFTscreen = TFT(cs, dc, rst);     /* TFT object */

#define BAR_WIDTH 5
#define BAR_HEIGHT 80
#define BAR_DISTANCE 2
#define BAR_N_MAX 23

#define FIX_COLOR ST7735_GREEN
#define NO_FIX_COLOR ST7735_RED
#define FG_COLOR ST7735_WHITE

#define TFT_WIDTH 160
#define TFT_HEIGHT 128

#define STRLEN 60

String cmd;                                // Output string to the ESP8266
String passcode;                           // Decrypted Network password
unsigned long markTime;                    // Time mark for the delay timer
int cri = 0;                               // Index for connection
char caString[36];                         // Character Array for formating String
char buff[12];                             // Character Array buffer to support sprintf conversion

float xLng = 0.0;                          // Default Longitude         0.0
float xLat = 0.0;                          // Default Latitude          0.0

SDClass  theSD;
int take_picture_count = 0;
int motion_count = 0;


boolean connectWifi();
static void Led_isActive(void);
static void Led_isPosfix(bool state);
static void Led_isError(bool state);
static void print_pos(SpNavData *pNavData);
void sendMotionAlert();
void motionDetectedInterrupt();
void waitTime(int milsec);

//-----------------------------------------------------------------------------
void setup() {
  /* put your setup code here, to run once: */

  pinMode(PIN_D03, INPUT);    // configure the interrupt pin (has 1K pullup)
  
  int error_flag = 0;

  /* Attach to servo motors 
     Note: The pins selected must support PWM. */
  p_servo.attach(PIN_D05);
  t_servo.attach(PIN_D06);

  /* Set the pan/tilt to 0 degrees */
  p_servo.write(90);    // pan
  t_servo.write(90);    // tilt


  /* Set serial baudrate. */
  if (MONITOR){ 
    Serial.begin(115200);
    while (!Serial)
    {
      ; /* wait for serial port to connect. Needed for native USB port only */
    }
  }
  
  if (MONITOR&&BROADCAST){
    Serial.println("Sony Spresense ESP8266-01 posting to ThingSpeak");
    Serial.println();
  }

  if(BROADCAST){
    Serial2.begin(115200);
    while(!Serial2){
      ; // wait for ESP8266 port to open/connect
    }
  }
  
  TFTscreen.begin();
  TFTscreen.setRotation(1);
  TFTscreen.background(0x00);
  TFTscreen.setRotation(3);
  TFTscreen.background(0x00);

  // write the title text to the screen
  // set the font color to white
  TFTscreen.stroke(255, 255, 255);
  // set the font size
  TFTscreen.setTextSize(2);
  // write the text to the top left corner of the screen
  TFTscreen.text("Portable", 20, 20);
  TFTscreen.text("Security", 20, 45);
  TFTscreen.text("Camera", 20, 70);  

  /* begin() without parameters means that
   * number of buffers = 1, 30FPS, QVGA, YUV 4:2:2 format */

  Serial.println("Prepare camera");
  theCamera.begin(1, CAM_VIDEO_FPS_5, CAM_IMGSIZE_QVGA_H, CAM_IMGSIZE_QVGA_V,
                  CAM_IMAGE_PIX_FMT_YUV422);

  /* Auto white balance configuration */
  Serial.println("Set Auto white balance parameter");
  theCamera.setAutoWhiteBalance(true);
 
  /* Set still picture parameters */
  Serial.println("Set still picture format");
  theCamera.setStillPictureImageFormat(
     CAM_IMGSIZE_QVGA_H,
     CAM_IMGSIZE_QVGA_V,
     CAM_IMAGE_PIX_FMT_JPG); 
  
  /* Wait HW initialization done. */
  sleep(3);

  /* Turn on all LED:Setup start. */
  ledOn(PIN_LED0);
  ledOn(PIN_LED1);
  ledOn(PIN_LED2);
  ledOn(PIN_LED3);

  /* Set Debug mode to Info */
  Gnss.setDebugMode(PrintInfo);

  int result;

  /* Activate GNSS device */
  result = Gnss.begin();

  if (result != 0)
  {
    Serial.println("Gnss begin error!!");
    error_flag = 1;
  }
  else
  {
    /* Setup GNSS */
    Gnss.select(QZ_L1CA);  // Michibiki complement
    Gnss.select(QZ_L1S);   // Michibiki augmentation(Valid only in Japan)

    /* Start positioning */
    result = Gnss.start(COLD_START);
    if (result != 0)
    {
      Serial.println("Gnss start error!!");
      error_flag = 1;
    }
    else
    {
      Serial.println("Gnss setup OK");
    }
  }

  /* Connect to WiFi AP. */
  connectWifi();

  /* Setup the ISR for the PIR sensor */
  attachInterrupt (PIN_D03, sendMotionAlert, RISING);
  
  /* Turn off all LED:Setup done. */
  ledOff(PIN_LED0);
  ledOff(PIN_LED1);
  ledOff(PIN_LED2);
  ledOff(PIN_LED3);

  /* Set error LED. */
  if (error_flag == 1)
  {
    Led_isError(true);
    exit(0);
  }
}

/* Callback function for live viewing */
void CamCB(CamImage img)
{
  waitTime(300);
  /* Check the img instance is available or not. */
  if (img.isAvailable())
    {
      /* If you want RGB565 data, convert image data format to RGB565 */
      img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565);

      /* Draw image on display */
      int w = 160;    //img.getWidth();
      int h = 120;    //img.getHeight();
      int x = 0, y = 0;
      int index = 0;
      uint16_t *buf16;

      buf16 = (uint16_t *)img.getImgBuff();

      for (y = 0; y < h; y++) {
        for (x = 0; x < w; x++) {
        index = y*640 + x*2;
        TFTscreen.drawPixel(x, y, buf16[index]); 
        }
      }

      theCamera.startStreaming(false, CamCB);

      /* Print position information. */
      print_pos(&NavData);

//      waitTime(10000);

    }
  else
    {
      Serial.print("Failed to get video stream image\n");
    }
   
}


/*
 * Print position information.
 */

static void print_pos(SpNavData *pNavData)
{
  char StringBuffer[STRING_BUFFER_SIZE];
  uint16_t fgcolor = NO_FIX_COLOR;

  /* clear the data boxes */
  //TFTscreen.fill(0xc618);   // hopefully gray
  //TFTscreen.rect(2, 2, 135, 58);
  TFTscreen.setTextSize(1);
   
  /* print time */
  snprintf(StringBuffer, STRING_BUFFER_SIZE, "%04d/%02d/%02d ", pNavData->time.year, pNavData->time.month, pNavData->time.day);
  Serial.print(StringBuffer);
  TFTscreen.stroke(FG_COLOR);
  TFTscreen.text(StringBuffer, 10, 10);

  snprintf(StringBuffer, STRING_BUFFER_SIZE, "%02d:%02d:%02d.%06d, ", pNavData->time.hour, pNavData->time.minute, pNavData->time.sec, pNavData->time.usec);
  Serial.print(StringBuffer);
  snprintf(StringBuffer, STRING_BUFFER_SIZE, "%02d:%02d:%02d", pNavData->time.hour, pNavData->time.minute, pNavData->time.sec);
  TFTscreen.text(StringBuffer, 80, 10);

  /* print satellites count */
  snprintf(StringBuffer, STRING_BUFFER_SIZE, "numSat:%2d, ", pNavData->numSatellites);
  Serial.print(StringBuffer);
  snprintf(StringBuffer, STRING_BUFFER_SIZE, "numSat:%2d", pNavData->numSatellites);
  TFTscreen.text(StringBuffer, 10, 25);

  /* print position data */
  if (pNavData->posFixMode == FixInvalid)
  {
    Serial.print("No-Fix, ");
    fgcolor = NO_FIX_COLOR;
  }
  else
  {
    Serial.print("Fix, ");
    fgcolor = FIX_COLOR;
  }
  if (pNavData->posDataExist == 0)
  {
    Serial.print("No Position");
    TFTscreen.stroke(NO_FIX_COLOR);
    TFTscreen.text("No Position", 10, 40);
  }
  else
  {
    Serial.print("Lat=");
    TFTscreen.stroke(FG_COLOR);
    TFTscreen.text("Lat=", 10, 40);
    Serial.print(pNavData->latitude, 6);
    Serial.print(", Lon=");
    TFTscreen.text("Lng=", 10, 55);
    Serial.print(pNavData->longitude, 6);
    TFTscreen.stroke(fgcolor);
    snprintf(StringBuffer, STRING_BUFFER_SIZE, "%03.4f", pNavData->latitude);
    TFTscreen.text(StringBuffer, 40, 40);
    snprintf(StringBuffer, STRING_BUFFER_SIZE, "%03.4f", pNavData->longitude);
    TFTscreen.text(StringBuffer, 40, 55);
  }

  Serial.println("");
}

/*
 * Print satellite information.
 */

static void print_condition(SpNavData *pNavData)
{
  char StringBuffer[STRING_BUFFER_SIZE];
  unsigned long cnt;

  /* Print satellite count. */
  snprintf(StringBuffer, STRING_BUFFER_SIZE, "numSatellites:%2d\n", pNavData->numSatellites);
  Serial.print(StringBuffer);

  for (cnt = 0; cnt < pNavData->numSatellites; cnt++)
  {
    const char *pType = "---";
    SpSatelliteType sattype = pNavData->getSatelliteType(cnt);

    /* Get satellite type. */
    /* Keep it to three letters. */
    switch (sattype)
    {
      case GPS:
        pType = "GPS";
        break;
      
      case GLONASS:
        pType = "GLN";
        break;

      case QZ_L1CA:
        pType = "QCA";
        break;

      case SBAS:
        pType = "SBA";
        break;

      case QZ_L1S:
        pType = "Q1S";
        break;

      default:
        pType = "UKN";
        break;
    }

    /* Get print conditions. */
    unsigned long Id  = pNavData->getSatelliteId(cnt);
    unsigned long Elv = pNavData->getSatelliteElevation(cnt);
    unsigned long Azm = pNavData->getSatelliteAzimuth(cnt);
    float sigLevel = pNavData->getSatelliteSignalLevel(cnt);

    /* Print satellite condition. */
    snprintf(StringBuffer, STRING_BUFFER_SIZE, "[%2d] Type:%s, Id:%2d, Elv:%2d, Azm:%3d, CN0:", cnt, pType, Id, Elv, Azm );
    Serial.print(StringBuffer);
    Serial.println(sigLevel, 6);
  }
}


/*
 *  Main program loop
 */
void loop()
{
  /* put your main code here, to run repeatedly: */

  static int LastPrintMin = 0;
  SpNavData *pNavData;                       // Pointer for NavData

  /* Blink LED. */
  Led_isActive();

  /* Check update. */
  if (Gnss.waitUpdate(-1))
  {
    /* Get NaviData. */

    Gnss.getNavData(&NavData);

    /* Set posfix LED. */
    bool LedSet = (NavData.posDataExist && (NavData.posFixMode != FixInvalid));
    Led_isPosfix(LedSet);

    /* Print satellite information every minute. */
    if (NavData.time.minute != LastPrintMin)
    {
      print_condition(&NavData);
      LastPrintMin = NavData.time.minute;
    }

    /* Update data for ThingSpeak */
    pNavData = &NavData;
    xLng = (float) (pNavData->longitude);                   // recast to float
    xLat = (float) (pNavData->latitude);                    // recast to float 
    Serial.println("data updated");   
  }
  else
  {
    /* Not update. */
    Serial.println("data not updated");
  }

  /* Post data to ThingSpeak */
  if (BROADCAST) postReadings();

  if (MONITOR) Serial.println("Start streaming");
  theCamera.startStreaming(true, CamCB);
  
}

boolean connectWifi(){
  ledOn(LED1); 
  if (WIFIMONITOR) Serial.println("\nAT> AT ------------------------------------------------------------");
  Serial2.println("AT");                        // Check:                     OK is returned
  waitTime(500);
  if (WIFIMONITOR) while (Serial2.available() > 0) Serial.write(Serial2.read());
  waitTime(100);

  if (WIFIMONITOR) Serial.println("\nAT> AT+RST --------------------------------------------------------");
  Serial2.println("AT+RST");                        // Check:                     OK is returned
  waitTime(5500);
  if (WIFIMONITOR) while (Serial2.available() > 0) Serial.write(Serial2.read());
  waitTime(1000);

  if (WIFIMONITOR) Serial.println("\nAT> AT+CIPMUX=0 -------------------------------------------------");
  Serial2.println("AT+CIPMUX=0");                   // Single connection mode:    OK is returned
  waitTime(500);
  if (WIFIMONITOR) while (Serial2.available() > 0) Serial.write(Serial2.read());
  waitTime(100);
  
  if (WIFIMONITOR) Serial.println("\nAT> AT+CWMODE=1 ---------------------------------------------------");
  Serial2.println("AT+CWMODE=1");                   // Client=1: OK is returned
  waitTime(500);
  if (WIFIMONITOR) while (Serial2.available() > 0) Serial.write(Serial2.read());
  waitTime(100);
 
  ledOn(LED2); 
  cmd="AT+CWJAP=\"";cmd+=SSID;cmd+="\",\"";cmd+=PASS;cmd+="\"";
  if (WIFIMONITOR){ Serial.print("\nAT> ");Serial.print(cmd); Serial.println(" ---------------------------------------");}
  for (cri=0; cri <= 3; cri++){
    Serial2.println(cmd);                   // Join Access Point:  OK is returned after about 3-4 seconds
    Serial2.flush();
    waitTime(6000);
    if (Serial2.find((char *)"OK")){
      if (MONITOR){
        if (WIFIMONITOR) Serial.println(cmd);
        Serial.print("Network Connected! ("); Serial.print(cri); Serial.println(")");
      }
      cri=5;
      break;
    }
  } 
  if (cri==3){
    if (MONITOR){
      while (Serial2.available() > 0) Serial.write(Serial2.read());
      Serial.println("AT+CWJAP failed...");
    }
  }
  if (WIFIMONITOR){
    while (Serial2.available() > 0) Serial.write(Serial2.read());
    Serial.write("AT+CWJAP=SSID");     // Do not expose decypted password
  }
  waitTime(100);

  if (WIFIMONITOR) Serial.println("\nAT> AT+CIFSR ------------------------------------------------------");
  Serial2.println("AT+CIFSR");                     // Check Connection status
  Serial2.flush();
  waitTime(2000);
  if (WIFIMONITOR){
    while (Serial2.available() > 0) Serial.write(Serial2.read());
    Serial.println();
  }
}

//---------------------------------------------------------------
void postReadings(){
  cmd = "AT+CIPSTART=\"TCP\",\"";
  cmd += HOST;
  cmd += "\",80";
  ledOn(LED3); 
  if (WIFIMONITOR) Serial.println("\nAT> AT+CISTART ----------------------------------------------------");
  for (cri=0; cri <= 3; cri++){
    Serial2.println(cmd);                   // Conenct to ThingSpeak: CONNECT is returned after about 1-2 seconds
    Serial2.flush();
    waitTime(4000);
    if (Serial2.find((char *)"CONNECT")){
      if (MONITOR){
        if (WIFIMONITOR) Serial.println(cmd);
        Serial.print("Thingspeak Connected! ("); Serial.print(cri); Serial.println(")");
      }
      cri=5;
      break;
    }
  } 
  if (cri==3){
    if (MONITOR){
      while (Serial2.available() > 0) Serial.write(Serial2.read());
      Serial.println("Thingspeak Connection failed...");
    }
  }

  cmd = "GET /update?api_key="; cmd += APIKEY;
  cmd += "&field1=";   sprintf(buff,"%.6f",xLat);     cmd += buff;
  cmd += "&field2=";   sprintf(buff,"%.6f",xLng);     cmd += buff;
  cmd += "\r\n";

  if (WIFIMONITOR){ 
    Serial.print("\nAT> AT+CIPSEND=");
    Serial.print(cmd.length()); 
    Serial.println(" ------------------------------------------------");
  }
  
  Serial2.print("AT+CIPSEND=");
  Serial2.println(cmd.length());                // Send length of IP string:  OK is returned  
  Serial2.flush();
  waitTime(2000);
  if (WIFIMONITOR) while (Serial2.available() > 0) Serial.write(Serial2.read());

  if (WIFIMONITOR){
    Serial.println("\n> GET -------------------------------------------------------------");
    Serial.print("> "); Serial.println(cmd);
  }
  
  Serial2.println(cmd);                         // Send Get/Post command:     SEND OK and +IPD,1:5CLOSED are returned
  Serial2.flush();
  waitTime(3000);
  
  if (WIFIMONITOR) while (Serial2.available() > 0) Serial.write(Serial2.read());

}

void sendMotionAlert(){
  cmd = "AT+CIPSTART=\"TCP\",\"";
  cmd += HOST2;
  cmd += "\",80";
  ledOn(LED3); 
  if (WIFIMONITOR) Serial.println("\nAT> AT+CISTART ----------------------------------------------------");
  for (cri=0; cri <= 3; cri++){
    Serial2.println(cmd);                   // Conenct to IFTTT Maker: CONNECT is returned after about 1-2 seconds
    Serial2.flush();
    waitTime(4000);
    if (Serial2.find((char *)"CONNECT")){
      if (MONITOR){
        if (WIFIMONITOR) Serial.println(cmd);
        Serial.print("IFTTT Maker Connected! ("); Serial.print(cri); Serial.println(")");
      }
      cri=5;
      break;
    }
  } 
  if (cri==3){
    if (WIFIMONITOR){
      while (Serial2.available() > 0) Serial.write(Serial2.read());
      Serial.println("IFTTT Maker Connection failed...");
    }
  }

  cmd = "GET /trigger/motion_detected_spresense/with/key/"; cmd += APIKEY2;
  cmd += "\r\n\r\n";

  if (WIFIMONITOR){ 
    Serial.print("\nAT> AT+CIPSEND=");
    Serial.print(cmd.length()); 
    Serial.println(" ------------------------------------------------");
  }
  
  Serial2.print("AT+CIPSEND=");
  Serial2.println(cmd.length());                // Send length of IP string:  OK is returned  
  Serial2.flush();
  waitTime(2000);
  if (WIFIMONITOR) while (Serial2.available() > 0) Serial.write(Serial2.read());

  if (WIFIMONITOR){
    Serial.println("\n> GET -------------------------------------------------------------");
    Serial.print("> "); Serial.println(cmd);
  }
  
  Serial2.println(cmd);                         // Send Get/Post command:     SEND OK and +IPD,1:5CLOSED are returned
  Serial2.flush();
  waitTime(3000);
  
  if (WIFIMONITOR) while (Serial2.available() > 0) Serial.write(Serial2.read());

}

//------------------------------------------------------------------------
// Supplement to delay() fucntion as delay() does not pause all actions and is asynchronous. 
//------------------------------------------------------------------------
void waitTime(int milsec){
  markTime = millis();
  while (millis()-markTime < milsec){
  }
}

//------------------------------------------------------------------------
/*
 * LED helper functions
 * LED0 CPU Active
 * LED1 PosFix
 * LED2 Error 
 */

static void Led_isActive(void)
{
  static int state = 1;
  if (state == 1)
  {
    ledOn(PIN_LED0);
    state = 0;
  }
  else
  {
    ledOff(PIN_LED0);
    state = 1;
  }
}

static void Led_isPosfix(bool state)
{
  if (state)
  {
    ledOn(PIN_LED1);
  }
  else
  {
    ledOff(PIN_LED1);
  }
}

static void Led_isError(bool state)
{
  if (state)
  {
    ledOn(PIN_LED3);
  }
  else
  {
    ledOff(PIN_LED3);
  }
}

void motionDetectedInterrupt() {
  
      // Send PIR Alert.
  
      Serial.println("Send Motion Alert");
      sendMotionAlert();
      Serial.print("motion_count = ");
      Serial.println(motion_count);

      motion_count++;
      
}

Credits

Ralph Yamamoto

Ralph Yamamoto

8 projects • 17 followers

Comments