Motion Sensing Nightvision Camera

I fancied figuring out if it was my cat or the neighbourhood cats pooping in my side alley (oh er missus!) so thought I’d find a use for my PiNoIR camera and that spare B+

I usually use RaspiMJPEG for webcam sorts of things like timelapse or somesuch, but it seems in a state of flux – the internal motion-detection system simply doesn’t work it would seem, and the external system is still using Motion and not the MMAL version, so its slow and can only cope with about VGA resolution on a B+, and it doesn’t seem to work in timelapse mode, and seems flaky at best anyway.

MotionEyeOS seemed like it was much more professional and motion-detection with timelapse worked really well, but again was limited to pretty low resolutions with weird aspect ratios. Also doesn’t seem to use motion-mmal anymore (think it did when it was motionpie).

Anyway, I figured the problem was software motion-detection using image analysis, so I decided to go the hardware route and use a PIR. So far I’ve come up with this Python script which is based on the excellent picamera module. It can do useful things like vertically flip the image (on the GPU!) which is handy as my Sainsmart camera module is a bit hard to mount the right way around!

Also the code uses interrupts so events don’t get missed and the CPU is mostly asleep. I chose to use 1080p resolution, but you could go up to to full 5MP (or 8MP on the new camera boards!) if you wanted to, forget 640×480

The only problem is that the PIR sensor doesn’t work through double-glazing, so I need to mount it in a box outside, which I was going to do eventually anyway.

#!/usr/bin/python
 
# import module
import picamera
import time
import RPi.GPIO as GPIO
 
# setup gpio mode
GPIO.setmode(GPIO.BCM)
PIR_PIN = 14
GPIO.setup(PIR_PIN,GPIO.IN)
 
# instantiate class
camera = picamera.PiCamera()
 
# vertical flip
camera.vflip = True
 
# set resolution
camera.resolution = '1080p'
 
# interrupt function
def onMotion(PIR_PIN):
    filename = time.strftime("image-%Y%m%d-%H%M%S.jpg")
    camera.capture(filename)
 
try:
    GPIO.add_event_detect(PIR_PIN, GPIO.RISING, callback=onMotion)
 
    # loop until interrupted
    while 1:
        time.sleep(100)
 
except KeyboardInterrupt:
    GPIO.cleanup()

You have to run the script using sudo, or from an init script like /etc/rc.local perhaps, and maybe set a directory to write the files to rather than just cwd.

Edit: just bought a v1.3 Raspberry Pi Zero and cable for this, as it should use a lot less power than the B+ and is a lot smaller to encase.

Pi Zero Print Server

After looking at wireless print servers costing £35+ I decided to make my own using my spare Raspberry Pi Zero (original without camera connector).

I needed a wifi dongle and USB connection for the printer, so have bought a Zero4U USB hub HAT and case, in fact I bought two of each as the shipping was fixed, so it came to £23 shipped from the Czech Rebuplic, so shouldn’t take too long to arrive.

I’ve got a spare 16Gb Class4 microSD card – we’ll have to see how that goes, otherwise I have a 32Gb Transcend on order. I’ve got a 5v 2A power supply and DC jack to microUSB adapter, as I don’t fancy the official power supply (its upside-down and looks fragile).

I’ve got Ralink RT5370 and Realtek RTL8188 wifi dongles on order, although I already have a RT5370 to use, but its nice to have spares that cost a Pound or two!

I installed the latest Raspbian Lite and added cups and sane to the installation.

The printer is a Brother DCP-7055 and I know it works with Ubuntu 12.04/14.04 as it used to be connected locally, however that was using x86-64, I’m not sure ARM is supported by Brother’s drivers, it seems printing at least will work with the HL-1250 ppd file or possibly the 7045N Postscript driver or brlaser, but almost definitely the brscan4 driver won’t work as its x86-only.

I don’t think I can connect a laptop to the miniUSB port on the Zero4U whilst its connected to the Zero, so to scan I’ll have to unplug the Zero from the printer unless Brother can be convinced by my constant tweets/emails to make ARM packages!

In other news my acrylic weld glue and needle bottles have arrived, so got to have a go at assembling the ESP8266 clock cases soon – and Dremel that button hole a bit more.

Update: Brother confirmed no current or future plans to support the ARM platform. The DCp-7500 worked out of the box with Raspbian’s built-in brlaser3 installation and DCP-7030 PPD file. No chance in getting the scanner working though, scanimage didn’t even detect it.

The Zero4U is pretty cool, although the acrylic case is a bit weak, took 5-6 days to arrive. The miniUSB port is blocked by the case so you can’t use it at the same time with a Zero and desktop PC anyway.

I assembled the acrylic case for the ESP8266 clock, it was a bit of a nightmare with the weld solvent, but it went together really well. I even managed to add some rails to make the lid removable.

TP4056 Charger Boards

I thought I’d write up my findings when using the TP4056 Li-Ion charging boards as a lot of people seem to be interested in them lately for charging circuits containing ESP8266 wifi chips which are rated from about 3.3-3.6v. Ideally a Li-Ion, LiPo or LiFePo4 battery. Note that the TP4056 can’t be used to charge LiFePo4 batteries (or NiMH, Alkaline etc).

First up, a couple of links to Julian Ilett’s videos of the older unprotected USB-mini board and another video comparing the newer protected USB-micro board which some people refer to as MP1405.

The boards I received seem to be a slight modification on the newer boards, they have a green LED instead of a blue one and the chip layout is different, but still use the DW01 battery protection IC and 8205 mosfet to (dis)connect the charger. There doesn’t seem to be any reverse-polarity protection like a diode. I paid £1.36 for five.

As my intended use was to allow my ESP8266 circuit to be enclosed in a box with no access to the battery, and be charged from any micro-USB source (computer, phone charger, powerbank, mains charger….) I swapped out the default 1k2 resistor (marked “122”, near IN-) which pulls around 900mA charge current, for a 2k2 one, which took the charge current down to about 540mA, only slightly above the average 500mA USB rating. It does slow down the charge rate, but would prevent poorly-built USB devices from pulling more current than they can handle. I used these 0603 metric (aka 0201 imperial) SMD resistors, which are just about hand-solderable – I actually managed to solder a through-hole resistor onto one board too!

There’s a couple of LED’s on the board:

Green on, red flashing = no battery
Red on, green off = charging
Green on, red off = charged
Both off = unplugged from USB

I was going to remove them to save quiescent current, but it turns out that when USB is disconnected (so you’re running off the battery) they are both turned off.

Also, you can still use your circuit when its plugged into USB, the “OUT+” outputs just under 4.2v irrespective of the battery’s charge (or even if the battery is disconnected). I’m not sure if it charges the battery whilst its powering a circuit, but believe so. Obviously the older mini-USB modules don’t have the output pads, they are purely for charging a battery, not powering a circuit.

One thing to note is that the modules seem to be optimised for being stored in a warehouse for a while, as they don’t actually turn on when you first connect a battery and measure about 150mV on all the pins. The answer is to plug them into USB, which then wakes the device. You can unplug them then if you like and they will output 3.7v from the battery on the OUT+ pin (or whatever the battery voltage is).

The board seems to get pretty hot whilst charging, which then cools as the battery voltage gets above about 4v and less current is flowing. The underside of my board got to 50c even with the 540mA charging current, according to a few websites this is the TP4056 chip itself, so I fitted a 6x6x3 heatsink that I had left over from a Raspberry Pi3 kit to the chip which seemed to drop the heat by about 5c, not sure if its worth it really.

Gadgets On Order

I’ve ordered a few gadgets to play with:

I plan to make another LED matrix clock and either use my Raspberry Pi camera or Android phone as an inspection camera for soldering.

Auto-BST for LED Clock

I’ve finally got around to adding British Summer Time detection to my LED matrix clock, I’ve also reduced flash wear and connection time by using the new persistent() method from the WiFi library. I’m still saving the UTC time to the RTC module, just displaying +/- the hour. The LiFePO4 battery is still putting out over 3.1v after 2 months.

The final code is below:

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "LedControl.h"
#include <Wire.h>
#include <RTClib.h>
 
// ntp server pool
IPAddress timeServerIP;
 
// ntp time stamp is in the first 48 bytes of the message
const int NTP_PACKET_SIZE = 48;
 
// buffer to hold incoming and outgoing packets
byte packetBuffer[NTP_PACKET_SIZE];
 
// create a udp instance
WiFiUDP udp;
 
// data, clock, cs, numdevices
LedControl lc = LedControl(D7,D5,D6,4);
 
// ds3231 constuctor
RTC_DS3231 RTC;
 
const int num[10][8] = {
    {0x00,0x78,0xcc,0xec,0xfc,0xdc,0xcc,0x78}, // zero
    {0x00,0xfc,0x30,0x30,0x30,0x30,0xf0,0x30}, // one
    {0x00,0xfc,0xcc,0x60,0x38,0x0c,0xcc,0x78}, // two
    {0x00,0x78,0xcc,0x0c,0x38,0x0c,0xcc,0x78}, // three
    {0x00,0x0c,0x0c,0xfe,0xcc,0x6c,0x3c,0x1c}, // four
    {0x00,0x78,0xcc,0x0c,0x0c,0xf8,0xc0,0xfc}, // five
    {0x00,0x78,0xcc,0xcc,0xf8,0xc0,0x60,0x38}, // six
    {0x00,0x60,0x60,0x30,0x18,0x0c,0xcc,0xfc}, // seven
    {0x00,0x78,0xcc,0xcc,0x78,0xcc,0xcc,0x78}, // eight
    {0x00,0x70,0x18,0x0c,0x7c,0xcc,0xcc,0x78}  // nine
};
 
void drawNum(int number, int display)
{
    lc.setColumn(display, 0, num[number][0]);
    lc.setColumn(display, 1, num[number][1]);
    lc.setColumn(display, 2, num[number][2]);
    lc.setColumn(display, 3, num[number][3]);
    lc.setColumn(display, 4, num[number][4]);
    lc.setColumn(display, 5, num[number][5]);
    lc.setColumn(display, 6, num[number][6]);
    lc.setColumn(display, 7, num[number][7]);
}
 
bool isBST(int year, int month, int day, int hour)
{
    // bst begins at 01:00 gmt on the last sunday of march
    // and ends at 01:00 gmt (02:00 bst) on the last sunday of october
 
    // january, february, and november are out
    if (month < 3 || month > 10) { return false; }
 
    // april to september are in
    if (month > 3 && month < 10) { return true; }
 
    // last sunday of march
    int lastMarSunday =  (31 - (5* year /4 + 4) % 7);
 
    // last sunday of october
    int lastOctSunday = (31 - (5 * year /4 + 1) % 7);
 
    // in march we are bst if its past 1am gmt on the last sunday in the month
    if (month == 3)
    {
        if (day > lastMarSunday)
        {
            return true;
        }
 
        if (day < lastMarSunday)
        {
            return false;
        }
 
        if (hour < 1)
        {
            return false;
        }
 
        return true;
    }
 
    // in october we must be before 1am gmt (2am bst) on the last sunday to be bst
    if (month == 10)
    {
        if (day < lastOctSunday)
        {
            return true;
        }
 
        if (day > lastOctSunday)
        {
            return false;
        }
 
        if (hour >= 1)
        {
            return false;
        }
 
        return true;
    }
}
 
// send an ntp request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
    // set all bytes in the buffer to 0
    memset(packetBuffer, 0, NTP_PACKET_SIZE);
 
    packetBuffer[0] = 0b11100011;   // li, version, mode
    packetBuffer[1] = 0;            // stratum, or type of clock
    packetBuffer[2] = 6;            // polling interval
    packetBuffer[3] = 0xEC;         // peer clock precision
 
    // 8 bytes of zero for root delay & root dispersion
    packetBuffer[12] = 49;
    packetBuffer[13] = 0x4E;
    packetBuffer[14] = 49;
    packetBuffer[15] = 52;
 
    // all ntp fields have been given values, send request
    udp.beginPacket(address, 123);
    udp.write(packetBuffer, NTP_PACKET_SIZE);
    udp.endPacket();
}
 
void displayDate(unsigned long unixtime)
{
    // power up led matrices
    for (int i=0; i<4; i++)
    {
        lc.shutdown(i,false); // come out of powersaving
        lc.setIntensity(i,5); // set brightness 0-15
        lc.clearDisplay(i);   // clear display
    }
 
    // handle british summer time
    DateTime nowntp = unixtime;
    int myhour;
    if (isBST(nowntp.year(), nowntp.month(), nowntp.day(), nowntp.hour()))
    {
        myhour = ((unixtime % 86400L) / 3600) + 1; // bst
    }
    else
    {
        myhour = (unixtime % 86400L) / 3600; // utc
    }
 
    // print hour to led
    if (myhour == 24)
    {
        drawNum(0,0);
        drawNum(0,1);
    }
    else
    {
        drawNum(myhour/10,0);
        drawNum(myhour%10,1);
    }
 
    // print minute to led
    int myminute = (unixtime % 3600) / 60;
    if (myminute < 10)
    {
        drawNum(0,2);
    }
    else
    {
        drawNum(myminute/10,2);
    }
    drawNum(myminute%10,3);
}
 
void gotoSleep()
{
    for (int i=0; i<4; i++)
    {
        lc.clearDisplay(i);
        lc.setIntensity(i,0);
        lc.shutdown(i,true);
    }
 
    // latch cs pin and pause to work around display0 being stuck high on sleep problem
    digitalWrite(D6, LOW);
    delay(2000);
 
    // sleep mcu forever
    ESP.deepSleep(0);
}
 
void setup()
{
    // setup ds3231
    Wire.begin();
    RTC.begin();
 
    // display rtc time on led matrices
    DateTime now = RTC.now();
    displayDate(now.unixtime());
 
    // connect to wifi network
    if (WiFi.SSID() != "myssid") {
        WiFi.begin("myssid", "mypassword");
        WiFi.persistent(true);
        WiFi.setAutoConnect(true);
        WiFi.setAutoReconnect(true);
    }
 
    // static ip, gateway, netmask
    WiFi.config(IPAddress(192, 168, 1, 2), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));
 
    // connect - could drain battery if never connects to wifi
    int wifitries = 0;
    while (WiFi.status() != WL_CONNECTED)
    {
        // give it a moment
        delay(1000);
 
        // only try 5 times before sleeping
        wifitries++;
        if (wifitries >5)
        {
            gotoSleep();
        }
    }
 
    udp.begin(2390);
 
    // get a random server from the pool
    WiFi.hostByName("europe.pool.ntp.org", timeServerIP);
 
    int cb = 0;
    int ntptries = 0;
    while (!cb)
    {
        // send an ntp packet to a time server
        sendNTPpacket(timeServerIP);
 
        // wait to see if a reply is available
        delay(3000);
        cb = udp.parsePacket();
 
        // only try 5 times before sleeping
        ntptries++;
        if (ntptries >5)
        {
            gotoSleep();
        }
    }
 
    // we've received a packet, read the data into the buffer
    udp.read(packetBuffer, NTP_PACKET_SIZE);
 
    // the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. first, extract the two words:
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
 
    // combine the four bytes (two words) into a long integer
    // this is ntp time (seconds since jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;
 
    // unix time starts on jan 1 1970. in seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
 
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;
 
    // update ds3231 from ntp unixtime
    RTC.adjust(DateTime(epoch));
 
    // display ntp time on led matrices
    displayDate(epoch);
 
    // ntp has a dot rtc does not
    lc.setLed(3, 7, 0, true);
 
    // wait five seconds before shutting down
    delay(5000);
    gotoSleep();
}
 
void loop()
{
}