Wednesday, April 17, 2019

A terrible security camera 3 - the client software

Notes:
  • Again, I'm using argparse.  The IP address now has to be the server's address, not the client's!
  • The client receives the image data, puts it into a PIL Image object, and then crops it to a square and resizes it to the dimensions of the Unicorn Hat (16 x 16).
  • If the --debug flag was used, the original and cropped/resized image are saved to files.
  • The client keeps sending requests to the server as fast as it can (which is not very fast given the other work it's doing).
If I run the client with the command below, the current webcam image is displayed on the Unicorn Hat (updating about twice a second) until I press CTRL-C.

pi@pibow:~/Python/camclient $ ./image-client.py -i 192.168.1.106 

 

 

The code


A terrible security camera 2 - the server software

A few notes on the server software:
  • I'm using the argparse module to allow me to change key information (IP address, port, debug information) from the command line.  The default is to use port 12345 on localhost.
  • The server simply takes a photo and sends it to the client.
  • The server runs forever until the process is stopped (ideally with CTRL-C).
An example of running the server: picam's IP address is 192.168.1.106 (I have set it up to have a static address on my home wifi). To run the server:

pi@picam:~/Python/camserver $ ./image-server.py -i 192.168.1.106 --debug
Server started on 192.168.1.106:12345


Because I've used the --debug flag, the server tells me it has started and the address and port number it's using.

 

The code


A terrible security camera 1 - the hardware

Raspberry Pi 3B in a Pibow case, with a Unicorn Hat HD attached and a diffuser on top of the case (machine name "pibow"). The Unicorn Hat HD comes with a dark diffuser, I prefer the look of the lighter diffuser which covers the whole top of the case.
Raspberry Pi Zero W in the official Pi case with a camera attached (machine name "picam").  The official case comes with a cable to attach the camera to the smaller connection on the Zero.

Raspberry Pi setup

A few notes on how I set up Raspberry Pi devices.

Static IP

My home router won't allocate addresses above 100 (I have set it up not to do so), so it is safe to use static IP addresses above that.  I keep a master list of allocated static addresses in /etc/hosts on my desktop (Linux) PC to avoid conflict with other addresses (doesn't do anything to avoid conflicts apart from having a master list in one place!). For example, a new Pi Zero W which I have called "pish" (it originally had a Pimoroni LED shim attached).

192.168.1.108 pish
 
Log in to pish and edit /etc/dhcpcd.conf to add the lines below, and reboot.


interface wlan0
static ip_address=192.168.1.108/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1
 
Assuming ssh is enabled on pish, on the desktop machine to allow login without password:

ssh-copy-id pi@pish

Shell

Add this line to .bashrc
 
export PATH=~/bin:$PATH
Create a ~/bin folder, and add this script to it in a file called update - allows quick system updates just by typing update at the command prompt.
#!/usr/bin/env bash

sudo apt update
sudo apt dist-upgrade
sudo apt autoremove
Create .inputrc and add this line to it (allows tab completion of file names ignoring upper and lower case variants).
set completion-ignore-case on

Editing

gvim (my preferred editor) gives a warning unless these packages are installed:

sudo apt install libcanberra-gtk*-module

Wednesday, November 8, 2017

Ubuntu 17.10

After a brief hiatus...

I've been using Ubuntu Mate for a while because of its simplicity, but recently bought a reasonably high-end PC for home use (i7 processor, huge hard disk, smaller SSD which makes booting and running a Windows virtual machine blisteringly fast) and tried out standard Ubuntu 17.10 at the weekend.  Ubuntu is now using Gnome 3 as the standard desktop, which is what made me look again.  It was so successful (with a bit of tweaking) that I'm now installing it on my work Linux PC (which these days is my spare machine, used for odd occasions when I need to try out something the university's locked-down Windows installation won't let me do).


  1. Install Ubuntu 17.10.  Takes about half an hour.
  2. Replace the hideous purple and orange wallpaper.
  3. sudo apt-get update; sudo apt-get dist-upgrade
  4. sudo apt-get install gnome-tweak-tool
  5. Using the tweak tool, remove desktop icons (Icons / Show Icons)
  6. sudo apt-get install chromium-browser chromium-codecs-ffmpeg-extra chrome-gnome-shell
  7. Start Chromium, go to extensions.gnome.org.  Install the browser extension so you can install shell extensions.
  8. Install Dash to Dock, and Dynamic Panel Transparency.
  9. Back to the tweak tool, and in Extensions change the DtD settings to display on the bottom, panel mode, and change the autohide settings so "push to show" is disabled.  In the Launchers tab move the applications button to the beginning of the dock.  Change the DPT settings (in the Background tab) to have custom opacity with an unmaximized opacity of 100%.  The latter is because I really don't like the effect where the top bar is semi-transparent if no windows are touching it, or a solid colour if a window is touching it.  Doesn't take effect until you log out and then log on again.
  10. Add the Numix theme and icons:
    sudo add-apt-repository ppa:numix/ppa
    sudo apt-get update
    sudo apt-get install numix-gtk-theme numix-icon-theme-circle
  11. Back to the tweak tool again, select the Numix theme and numix circle icons. 
Addendum:
Icons from the dash/dock were appearing in the lockscreen and activities view - the solution was
apt-get purge gnome-shell-
extension-ubuntu-dock
which removes an alternative dock.

Tuesday, July 30, 2013

Twitter, again


Using the Arduino ethernet shield on the Mega 2560 Arduino, this displays tweets.  But not directly - Twitter's new API requires SSL, and the Arduino isn't up to doing that.  So I set up a CGI program on my desktop computer which checks a twitter feed, and returns a plain text version of the username and message.  Code for the CGI program:

#!/usr/bin/python

import twitter, sys, time, pickle

class Timeline:
    def __init__ (self):
        self.seen = set()
        self.cache = []

    def add_to_cache (self, statuses):
        for d in [s.AsDict() for s in statuses]:
            if d["id"] not in self.seen:
                self.seen.add(d["id"])
                self.cache.append(d)

    def __iter__ (self):
        return self

    def next (self):
        if len(self.cache) == 0:
            raise StopIteration
        else:
            return self.cache.pop()
 
oauth_file = open("access_token.txt", "r")
akey = oauth_file.readline().rstrip()
asec = oauth_file.readline().rstrip()

consumer_file = open("consumer_keys.txt", "r")
ckey = consumer_file.readline().rstrip()
csec = consumer_file.readline().rstrip()

try:
    api = twitter.Api(consumer_key = ckey, consumer_secret = csec,
        access_token_key = akey, access_token_secret = asec)
except Exception as e:
    print e
else:
    try:
        timeline = pickle.load(open("timeline.dat"))
    except:
        timeline = Timeline()

    timeline.add_to_cache(api.GetUserTimeline("LE17RH"))

    print "Content-type: text/plain\n"
    try:
        x = timeline.next()
        print x["user"]["screen_name"]
        print x["text"]
    except StopIteration:
        pass

    pickle.dump(timeline, open("timeline.dat", "w"))

The Timeline class is an iterator because in an earlier incarnation I was loading a lot of tweets and wanting to iterate over them, it isn't necessary for this program which just returns a plain text rendition of one username and message.

Code for the Arduino:

#include <SPI.h>
#include <Ethernet.h>
#include <LiquidCrystal.h>

byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0x7F, 0xB6 };
const IPAddress ip(143,210,109,74);
const IPAddress dnsserver(143,210,12,154);
const IPAddress server(143,210,108,92);
const char get_header[] = "GET /cgi-bin/twittino.cgi";
const char server_header[] = "Host: 143.210.108.92 80";

EthernetClient client;
LiquidCrystal lcd(8, 9, 17, 16, 15, 14);

const int SDSELECT = 4;
const int PIN_G    = 21; // Pins on Mega board!
const int PIN_R    = 20;
const int PIN_B    = 19;

void setup() {
  pinMode(PIN_R, OUTPUT);
  pinMode(PIN_G, OUTPUT);
  pinMode(PIN_B, OUTPUT);
  pinMode(SDSELECT, OUTPUT);
  digitalWrite(PIN_R, HIGH);
  digitalWrite(PIN_G, LOW);
  digitalWrite(PIN_B, HIGH);
  digitalWrite(SDSELECT, LOW);
  Serial.begin(9600);  
  lcd.begin(16, 2);
  Ethernet.begin(mac, ip, dnsserver);
  delay(1000);
  digitalWrite(SDSELECT, HIGH);
  Serial.println(Ethernet.localIP());
  digitalWrite(PIN_G, HIGH);
}

void loop()
{
  static int counter = 1;
  char username[30];
  char tweet[141];
  
  Serial.println(counter++);
  
  if (client.connect(server, 80)) 
  {
    // Serial.println("Connected");
    // Make a HTTP request:
    client.println(get_header);
    client.println(server_header);
    client.println("Connection: close");
    client.println();
    int ulen = 0;
    int tlen = 0;
    boolean username_found = false;
    while (client.connected())
    {  
      if (client.available()) 
      {
        char c = client.read();
        if (!username_found)
        {
          if (c == '\n')
          {
            username_found = true;
            username[ulen] = '\0';
          }
          else
          {
            if (c != '\n') username[ulen++] = c;
          }
        }
        else
        {
          if (c == '\n') 
          {
            tweet[tlen++] = ' ';
          }
          else
          {
            tweet[tlen++] = c;
          }
          switch(c)
          {
            case 'R' : digitalWrite(PIN_R, LOW); break;
            case 'G' : digitalWrite(PIN_G, LOW); break;
            case 'B' : digitalWrite(PIN_B, LOW); break;
            case 'r' : digitalWrite(PIN_R, HIGH); break;
            case 'g' : digitalWrite(PIN_G, HIGH); break;
            case 'b' : digitalWrite(PIN_B, HIGH); break;
          }
        }
      }
    }
    client.stop();
    // Serial.println("Disconnected");
    tweet[tlen] = '\0';
    if (ulen != 0 && tlen != 0)
    {
      Serial.print("Username : ");
      Serial.println(username);
      Serial.print("Tweet    :\n");
      Serial.println(tweet);
      lcd.setCursor(0, 0);
      for (int i=0; i < ulen; i++)
      {
        lcd.print(username[i]);
      }
      for (int i=0; i <= tlen; i++)
      {
        lcd.setCursor(0, 1);
        for (int j=0; j < 16; j++)
        {
          if (j + i < tlen) lcd.print(tweet[j+i]);
        }
        lcd.print(' ');
        delay(500);
      }
    }
    else
    {
      Serial.println("Nothing.");
    }
  } 
  else 
  {
    Serial.println("Connection failed.");
  }
  delay(10000); // Check every 10 seconds
  lcd.clear();
}

Things to note: the SD card, if present, needs to be deactivated (by setting SD_SELECT, pin 4, to LOW) before setting up the ethernet connection.  In addition to the LCD panel, I have one of my RGB LED units connected, and if there is a capital R, G or B in the tweet, the appropriate colour is displayed (the lowercase equivalents turn the colour off).  The username is displayed on the top row of the LCD panel, and the tweet scrolls along the bottom row.

 

Friday, July 26, 2013

Shift Registers

I bought a "Nano 3.0 for Arduino" for eight quid - a neat little thing that reminds me of the Hexbug Nanos I bought my niece and nephew for Christmas a couple of years ago, but is rather more interesting for grownups.  It fits directly into a breadboard, and is powered and programmed through a USB lead (standard "mini A" connector).


The pins are:

Top row: digital pins D12 to D2 (D2 is just above the TX LED), ground, reset, RX and TX (RX and TX are D0 and D1 on an Arduino Uno).

Bottom row: digital 13, 3.3 V, REF (analogue reference, labelled AREF on bigger boards), analogue inputs A0 to A7 (two more than on the Uno, A7 is just below the LED), 5 V, reset, ground, and input voltage (VIN).

This is almost, but not quite, the same layout as the official Arduino Nano, and considerably cheaper.  I stuck it on a breadboard with a couple of shift register chips (74HC595), and used it to control some RGB LED units.

The RGB units have three LEDs with a common anode, so controlling four of them requires twelve bits.  I've used two shift registers, so that gives sixteen bits to play with.

A diagram is below.

Things to note: the board shown in the diagram doesn't have the same pin order as mine, and my breadboard (see below) has a very different layout but the circuit is the same.  The connections from the board are: 5V pin to the top power rail (which is then linked to the lower power rail in the middle, and the bottom power rail), ground pin to the top ground rail (similarly linked to the middle and bottom), and the SPI connection to the first shift register: blue wire links D2 to the data pin on the 74HC595, green wire links D4 to the latch pin, and yellow wire links D3 to the clock pin.

The two 74HC595 chips have the clock and latch pins linked (horizontal green and yellow wires), and the serial output from the first goes to the data pin on the second (horizontal blue wire below the yellow one).  This means that when two bytes are sent into the first chip, the first byte is displaced into the second chip (you can chain these things even further - so if you had four linked chips and sent four bytes into the first one, the fourth chip would contain the first byte sent, the third chip the second byte, the second chip the third byte and the first chip the fourth byte).

I have only shown the connections for one of the LED units - the horizontal red, blue and green wires connect from pins Q3, Q2 and Q1 of the first 74HC595 to the cathodes of one of the LED units.  Pins Q7-Q5 of the first 74HC595 go to the RGB cathodes of the second unit, and the third and fourth units are connected to the same pins on the second 74HC595.  On my breadboard (see below) there are no wires making these connections - I just bridged the pins of the 74HC595 chips to the LED units with resistors (270 Ω).

I'm not using pins Q0 or Q4 on either 74HC595 - they aren't needed (and Q0 is in a slightly inconvenient position, on the other side of the chip to the other seven output pins, next to the data pin).


Code:

const int latchPin = 4;
const int clockPin = 3;
const int dataPin  = 2;

const byte red     = 0x08;
const byte green   = 0x02;
const byte blue    = 0x04;
const byte yellow  = red   | green;
const byte magenta = red   | blue;
const byte cyan    = green | blue;
const byte white   = red   | green | blue;
const byte black   = 0x00;

const byte sequence[8] = {black, red, yellow, green, 
  cyan, blue, magenta, white};

void put_four_leds(byte leds[4])
{
    byte a = leds[0] | (leds[1] << 4);
    byte b = leds[2] | (leds[3] << 4);
    
    digitalWrite(latchPin, LOW);
    // Flip bits because low is on, high is off
    // Second pair goes first because that byte is shifted
    // through to the second shift register. 
    shiftOut(dataPin, clockPin, MSBFIRST, ~b);
    shiftOut(dataPin, clockPin, MSBFIRST, ~a);
    digitalWrite(latchPin, HIGH);
}

void setup() 
{
  //set pins to output so you can control the shift register
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
}

void loop() 
{
  byte led_array[4];
  int  i, j;
  
  for (i=0; i<8; i++)
  {
    for (j=0; j<4; j++)
    {
      led_array[j] = sequence[(i + j) % 8];
    }
    put_four_leds(led_array);
    delay(500);
  }
}