Who Stole my cat's food?

I am trying to build a system that will monitor my cat’s food bowl. Squirrels and rats steal from there all the time.
System overview:
An arduino with motion detectors is overlooking the bowl. When presence is detected, the arduino sends a serial command to the PC by USB. At that time, processing sketch is starting a SaveFrame… etc for about 10 sec.
THe system described is OK, but is late. I would like to see how the rat is coming in my cat’s room in the first place. I would like to save the last 30 sec before the event happens.

For that I would want to:

  1. Record all frames continuously but delete the ones that are NOT the last 900 frames (30sec).
    Is there a file management for that?
  2. If the event is triggered by the arduino, I would like to move the last 900 frames in a new directory for later examination.
    Again, a file management would come handy to move the files in another directory.
    So at the end of 24 hours I would end up with 2-3 30 second clips to examine based on the 2-3 triggered events.

I can handle well: Arduino presence detection and USB communication.
Need suggestion on saving and moving the batches of saved frames.

Thanks
Mitch

hard drive access is expensive.

You could store the images in the RAM (ArrayList) that you would overwrite throughout

in case of the event, you make new folder with date time stamp and store to hard drive

also a RAM Disk usage might be a way for file storage.
( like all ad-hoc data collections ( using python ) on Raspberry Pi i do there / by default available RPI3B /run/shm/ ?60MB … 450MB? )

for the file and overwriting the operating system will take care, if you choose right name.
but as that will give a kind of ring buffer you need to know where it starts/ends.

String fn0 = "data/snap";
int x = 0, xend = 900;
String fn1 = nf(x, 3);
String fn2 = ".png";
String outfile = fn0+fn1+fn2;
String pointfile = "data/pointer.txt";
boolean record = true;

void setup() {
  println("recording: "+record);
}

void draw() {
  background(200, 200, 0);
  recording();
}

void recording() {
  if ( record ) {
    x++; 
    if ( x >= xend ) x = 0;
    fn1 = nf(x, 3);
    outfile = fn0+fn1+fn2;
    text(outfile, 0, 10);
    save(outfile);
    String[] info = {outfile, ""};
    saveStrings(pointfile, info);
  }
}

void mousePressed() {
  if ( record ) { 
    record = false;
    println("last: "+outfile);
    // here should copy the whole data directory OR make the movie
  } else { 
    record = true; 
    x = 0;
  }
  println("recording: "+record);
}

that gives 900 image files and one pointer file.
now, that you do all the time as a recording of the last 900 ( here draw loops )
and in case of a event you can wait n sec ( or do 450 loops more ) and then stop that storing…
in above example i only on mouse click stop / start

1 Like

KLL

Thanks for the guidance and help. I am not your sharpest programmer, so please bear with me. Your code does not include any “saveFrame” so I dont really know what to do.

Here is what I implemented. It does save 900 frames after the trigger and it stops. Still struggling how to save 900 frames before the serial trigger.

Thanks

/**
 * Getting Started with Capture.
 * 
 * Reading and displaying an image from an attached Capture device. 
 */

import processing.video.*;
import java.awt.Font;
import java.awt.Rectangle;
PFont f;
PFont font;
int w= #FFFFFF; // white
int y= #FCF442; //yellow
int g= #A0FFA3; //green
int b= #64E1FF; // blue
int m= #CC15D3; // Magenta
int o= #FF6C67;  // orange
int i= #B767FF; // indigo
int r= #FC0B03; // red
int bk = (#212121);        
import processing.serial.*;
Serial ArduCOM_Port; 

Capture cam;
int IncomingByte;

String fn0 = "data/snap";
int x = 0, xend = 200;
String fn1 = nf(x, 3);
String fn2 = ".png";
String outfile = fn0+fn1+fn2;
String pointfile = "data/pointer.txt";
boolean record = true;

void setup() {
    size (640, 480, JAVA2D);
     font = loadFont("AgencyFB-Bold-200.vlw");

  String[] cameras = Capture.list();

  if (cameras == null) {
    println("Failed to retrieve the list of available cameras, will try the default...");
    cam = new Capture(this, 640, 480);
  } if (cameras.length == 0) {
    println("There are no cameras available for capture.");
    exit();
  } else {
    println("Available cameras:");
    printArray(cameras);

    // The camera can be initialized directly using an element
    // from the array returned by list():
    cam = new Capture(this, cameras[7]);
    // Or, the settings can be defined based on the text in the list
    //cam = new Capture(this, 640, 480, "Built-in iSight", 30);
    
    // Start capturing the images from the camera
    cam.start();
    
  }
  frameRate(30);
    ArduCOM_Port = new Serial(this, "COM4", 115200);
    record=false;
}  //end setup

void draw() {
 // background(bk); 
  if (cam.available() == true) {
    cam.read();
  }
  image(cam, 0, 0, 640, 480);
  
  if ( ArduCOM_Port.available() > 0 ) { 
    IncomingByte = ArduCOM_Port.read(); 
    println (IncomingByte);
    if (IncomingByte==1)
    record=true;
    else 
    record=false;
    
  }
    
    
   textFont(font, 35); 
  fill(r);
  
   if (record)
   {saveFrame("Demos/Demos-######.png");
    x++; 
    text( "R ",10,460);
    if ( x >= xend ) 
    {x = 0;
    IncomingByte=0;
    record=false;
    }
   }
   
  else
  {text( "M ",10,460);
  }
}

void keyPressed() {
  if (key == CODED) {
     background(bk); 
    if (keyCode == UP) {
       textFont(font, 35); 
      fill(r);
      record= true;
      
    } else if (keyCode == DOWN) {
       record= false;
      text( "M",10,460);
    }
  }
}

the idea was to record all the time, and at THE EVENT
stop overwriting the files / copy the directory / make the movie …

  • that requires also to take the above RAM usage ideas
    images in RAM or files in RAM DISK, seriously
    as it could damage your drive

but you start the recording at THE EVENT
what will never give info about the time prior


your arduino code seems to also directly set record true/false
that would never allow any time frame thinking


also i tested here:

    //save(outfile);
    saveFrame(outfile);

same result.

i assume you not run my code as posted above?


also you could guess that i love cats too,
so can you post some photos of yours?

Here is stripping the concept down to almost the simplest possible ring buffer for saveFrame.

// https://discourse.processing.org/t/who-stole-my-cats-food/13895/6
int clip = 0;
int bufferCount = 6;

void setup() {
  frameRate(2);
  colorMode(HSB,90);
  strokeWeight(5);
}

void draw() {
  // draw something
  translate(frameCount*4%width, 0);
  stroke(frameCount%60,100,100);
  line(0,0,0,height);

  // save to a buffer file --
  // % is what loops across the files
  String frameName = str(clip)+"_"+str(frameCount % bufferCount);
  saveFrame(frameName+".png");
}

void keyReleased() {
  // advance the clip file name part --
  // this automatically "saves" the old buffer
  // because those files are no longer overwritten
  clip++;
}

This doesn’t rename the files on disk – it just keeps overwriting them in a ring, then switches to a new buffer when you trigger an event.

For the next step you can rename the files when you finalize the buffer (e.g. on the event) to order them correctly – although honestly, if you are checking pngs for rats you can probably just sort your file browser by date modified.

1 Like

Thank you.
I got it to work with serial input, I am just incrementing the folder name on that date with each serial event.

 if ( ArduCOM_Port.available() > 0 ) { 
    IncomingByte = ArduCOM_Port.read(); 
    println (IncomingByte);
    if (IncomingByte==1)
    {
      println("last: "+outfile);
      ClipIncr++;
      IncomingByte=0;
    }
  }

void recording() {
  if ( record ) {
    x++; 
    if ( x >= xend ) 
    {
      x = 0;
    }
    TimeStampFun();
    // println (TimeStamp);
    fn1 = nf(x, 4);
    outfile = ClipIncr+"_"+DateStamp+fn0+fn1+fn2;
    fill(0, 0, 0);
    rect(0, 0, 120, 45, 7);
    //textFont(font,16); 
    fill(w);
    text(outfile, 5, 15);
    text(DateStamp +" "+ TimeStamp, 5, 35);

    save(outfile);
    String[] info = {outfile, ""};
    saveStrings(pointfile, info);
  }
}

A couple of problems remain:

  1. The “Movie Maker” from tools will assemble a film based on the file sequence name. So I end up with a broken film. (If my event happen at frame 97, the movie maker will not regard that as the begging of the film if there is a frame 0)

  2. I would like the sketch to make me the film. I searched a lot. Everyone points to the tool. Is there a function that will take the pngs and make a .MOV?

Thanks everyone

Jeremy, I will study your suggestion later. Thanks

    if (IncomingByte==1)
    {
      println("last: "+outfile);
      ClipIncr++;
      IncomingByte=0;
    }

in your case ( combine my counter / jeremydouglass clip / your DateStamp // ideas )
you can just reset the x = 0; too, when you change the clip number,
so the movie starts with 000.

also a little MOD to the clip idea,
not use it as filename, use it as path? ( tested “saveFrame” makes the path automatically )


it does make the use of movie maker easier, but not solve the pointer info ( now inside its path ) problem


nevertheless, if xend is still 900
every THE EVENT creates 900 more files and your drive will get full.
and if there is no event you burn your drive ( SSD ? ) as you still
store to the sketch path?


with

nf(clip,3)
clip++

and clip unlimited can get max 1000 sub directories,
again you could limit your clips like

if ( clip++ >= 9 ) clip = 0;
x=0;

get only the last 9 event ( and the running directory )
nice but creates the same ring buffer problem again for the directories

  • you must look for the date of the pointer file to know whats the newest one.
// rev 0 make filename
// rev 1 make point file
// rev 2 try clip as path idea what fits to moviemaker
// rev 3 use new win10 ramdisk 
// https://www.youtube.com/watch?v=KvjH6p5pB5Q
// http://memory.dataram.com/products-and-services/software/ramdisk

String d0  = "R:/TEMP/";                   // use my new WIN 10 / RAMDISK ( with save / recover option so not loose files on reboot!)
//String d0  = "";                           // now use sketch path
int clip = 0, clipend = 3;                 // create max 4 subdirs
String p0  = "event"+nf(clip, 3)+"/";      // make a path
String fn0 = "snap";
int fps = 2;                               // 2 frames / pictures per sec 
int x = 0, xend = 9;                       // and max 10 files == 5 sec recording time
String fn1 = nf(x, 3);
String fn2 = ".png";
String outfile = p0+fn0+fn1+fn2;           // filename
String pointfile = p0+"pointer.txt";       // pointer file at same path
boolean record = true;                     // init mode RECORDING

void setup() {
  size(300, 100);
  println("recording: "+record);
  frameRate(fps);
}                                          // end function setup

void draw() {
  background(200, 200, 0);
  recording();
}                                          // end function draw

void recording() {                         // called from main draw()
  if ( record ) { 
    fn1 = nf(x, 3);
    if (  x++  >= xend ) x = 0;            // so we start with "000"
    p0  = "event"+nf(clip, 3)+"/";         // use clip counter sub dir
    outfile   = d0+p0+fn0+fn1+fn2;         // drive / path / file + counter + .filetype
    pointfile = d0+p0+"pointer.txt";       // pointerfile there too
    text(outfile, 0, 10);                  // test show filename in canvas ( to check in files later )
    //save(outfile);
    saveFrame(outfile);                    // save every frame
    String[] info = {outfile, ""};
    saveStrings(pointfile, info);          // and record that name in first line of a pointer file
  }
}                                          // end function recording

void mousePressed() {
  if ( record ) {                          // toggle recording
    record = false;
    println("last: "+outfile+" \nrecording: "+record+" ,for start recording must click again");
  } else { 
    record = true; 
    if (clip++ >= clipend ) clip = 0;      // increment and limit recorded clips ( path )
    x = 0;                                 // reset filename number
    println("recording: "+record);
  }
}                                          // end function mousePressed

RAM_DISK_file_save_limited