Making a Security Camera using Raspberry Pi

My parents needed a Security Camera at their workplace after a string of thefts and they were looking at old school analog CCTV cameras which operate at 480i resolution and record to a DVR. In this day and age, 480i is unacceptable but since those cameras are cheap (~5000Rs per camera), people tend to go for them although the cost of DVR and LCD is additional. So I thought I’d make an HD Security Camera myself as a project. My goals were to record at least 720p footage, have a footage backup of 7 days, have power consumption low enough so it could run 24/7 on UPS and have minimal amount of wires exiting the camera (just 1 Power Cable, no LAN cable).

I settled on using Raspberry Pi board for all my processing needs. If you don’t know about Raspberry Pi, its a wonderful project that manufactures this tiny board (slightly bigger than a credit card) that works as a full fledged computer which can run a full Linux OS. People have used it to build a host of wonderful projects, all the way from robots to flying drones!

Among a list of accessories for Raspberry Pi is a Camera module (sold separately) which takes 1080p videos and pictures. Perfect for my Security Cam!

Here’s the list of all of the equipment I used for making the Security Camera (along with price). I live in Gujranwala and all of the stuff listed was bought online or from local shops:

Raspberry Pi Model B+ (Price: Rs 5,500)

I bought the latest Raspberry Pi Model B+ online from Unique Gadgets (uniquegadgets.com.pk). The website offers “Cash-on-Delivery” payment so you don’t need to own a credit card to purchase from them. Their customer support is also pretty friendly. I bought the wrong type of SD Card for my Raspberry Pi. The guy called and suggested I order the correct type instead of shipping me the wrong type.

You can check the Hardware Specs for the board here: http://www.raspberrypi.org/products/model-b-plus/

20150120_180708

Raspberry Pi Camera Module (Price: Rs 4,500)

I also bought this online from Unique Gadgets.

The Raspberry Pi Camera Module Features:

  • Fully Compatible with Both the Model A/Model B/Model B+ Raspberry Pi
  • 5MP Omnivision 5647 Camera Module
  • Still Picture Resolution: 2592 x 1944
  • Video: Supports 1080p @ 30fps, 720p @ 60fps and 640x480p 60/90 Recording
  • 15-pin MIPI Camera Serial Interface – Plugs Directly into the Raspberry Pi Board
  • Size: 20 x 25 x 9mm
  • Weight 3g

rasp_cameramodule

Transcend 8GB Micro SDHC Class 10 (Price: Rs 1,150)

This acts as the onboard Hard Drive for Raspberry Pi. It carries the OS (Raspbian which is based on Debian) and the remaining space can be used to store data. Also bought online from Unique Gadgets.

Transcend 8GB MicroSDHC Class 10-500x500

Power supply (Price: Rs 450)

The Raspberry computer does not come with any power supply, you have to get one on our own. Any 5V power supply with a micro-USB plug can do the job as long as it supplies at least 1A of power. I bought a cheap one from a local computer market. But get a good quality one if you can as low/unstable power is the no.1 problem that can ruin your project.
2 AMP Power Adaptor - 1-500x500

TP-Link WNL-722N USB WiFi Adapter (Price: Rs 950)

The Raspberry Pi board has a LAN port on-board and can be connected to a network via a LAN Cable. But since I want to mount the Security Camera on a wall, I don’t want a LAN Cable snaking all the way to a Router. Instead we’ll use a TP-Link WNL-722N USB WiFi adapter that can plug into any of Raspberry Pi’s 4 USB 2.0 ports. This particular model is supported by Raspbian (Raspberry Pi’s OS) and has a bandwidth capacity of 150 Mbps. I bought it from the local computer market.

20150120_180949 20150120_180931

Toshiba 320GB External Hard Drive (Price: Rs 4,500)

We need ample amount of storage for the footage that our Security Cam records. I had a spare Toshiba 320GB External Hard Drive lying around, which can hold around a week’s worth of 720p footage. You can buy according to your needs. We are going to open the casing for the External Hard Drive because its too large for our camera housing.

20150120_181048

Powered USB Hub (Price: Rs 950)

Our External Hard Drive takes a lot of juice, something the Raspberry Pi can’t supply considering it itself is running on a 5V/1A power source. So I bought a Powered USB Hub from the local computer market that takes a seperate 5V/1A charger. That will take care of the power needs for our Hard Drive, WiFi adapter plus any peripherals we might want to add in the future like a 3G USB Modem (PTCL EVO Wingle). The Hub also needs to be disassembled to fit into our camera housing.

20150120_181118 20150120_180837

Casing/Housing for Security Camera (Price: Rs 1,500)

We need an empty casing to house all of our equipment. There are “dummy” casings available in the market that fit our needs perfectly. Look for shops that work with security equipment, they usually have spare dummies lying around. I bought an unused piece with mounting stand from Trust Plaza in Gujranwala.

20150120_180907 20150120_180911

Custom Iron Plate with screw holes (Price: Rs 400)

My specific casing has screws which are too wide to mount the Pi on them. I needed a custom plate with screw holes according to dimensions of the casing, that I could securely mount in the casing. The plate also needed screw holes according to dimensions of Pi, so that the board could be mounted on the plate.

Luckily I live near an entire market full of Iron merchants and their associated works. I bought a plain iron plate, had it cut to my casing’s specifications. I had the front end molded to stand at right angles in a press where they mold pipes and stuff. They did it for free! I took it to a welder who used a gas flame torch to cut the front end into a nice curved shape so that it would fit into my casing. Next I took it to an acquaintance who works as a die manufacturer. He machine tooled nice screw holes on the plate according to design specifications (2.5mm +- 0.05mm): 4 for the casing, 4 for the Raspberry Pi on the base and 2 on the front end for the Camera Module. All of it cost 400 Rs and took around 4 hours.

20150120_180620 20150120_180644

Miscellaneous Items

  • Screws with nuts
  • Rubber Bands
  • Reflective Paper
  • Plastic sheets

20150120_181237 20150120_180733 20150120_181735

The entire cost came around to Rs 20,000. For comparison: a Wireless HD IP Camera + Storage + Router starts above Rs 40,000.

This was my first time working with Raspberry Pi, although I have a decent level of experience working with Linux. Setting up the Raspberry Pi is extremely easy. Here is an excellent guide to setting up the Pi: http://neil-black.co.uk/the-updated-raspberry-pi-beginners-guide

I connected the Raspberry Pi to my home router via LAN cable and SSH’d into it. I connected my External Hard Disk and USB WiFi Adapter to the USB Hub and connected the Hub to the Pi via USB. The OS recognized the WiFi adapter immediately but the External Hard Drive was giving me problems. It would make a weird clicking noise and sometimes show up and some times it wouldn’t get recognized. After a lot of troubleshooting, I found out that my charger for the USB Hub was 4.5V/1A. Switching to 5V/1A solved the HDD issues but they would crop up again from time to time. So I went back a got a 5V/1.5A charger but that had the wrong connector. Replacing the connector with the one from the old charger was an easy fix and then onward the Hard Drive worked smoothly.

With all the equipment connected and working smoothly, I wanted Pi to work as a WiFi Access Point (AP). I wanted to be able to connect to the Pi directly without using any router. After a lot of searching and trial-and-error, I found these two guides that allowed me to set up the Pi as an AP: http://itsacleanmachine.blogspot.com/2013/02/wifi-access-point-with-raspberry-pi.html and https://learn.adafruit.com/setting-up-a-raspberry-pi-as-a-wifi-access-point/overview

They use hostapd to setup a WiFi AP and isc-dhcp-server to hand out IP addresses. I tried many other programs but these two worked the best. Both of these guides have detailed, step-by-step instructions that do not require any special skills to follow. For reference, here are my working configuration files:

/etc/hostapd/hostapd.conf

interface=wlan0
driver=nl80211
ssid=[enter the WiFi AP name you want]
hw_mode=g
channel=6
auth_algs=1
#wme_enabled=1
#ieee80211n=1
#ht_capab=[HT40+][SHORT-GI-40][DSSS_CCK-40]
wpa=2
wpa_passphrase=[enter the passphrase you want]
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
wpa_ptk_rekey=600
macaddr_acl=0

/etc/dhcp/dhcpd.conf

#
# Sample configuration file for ISC dhcpd for Debian
#
#

# The ddns-updates-style parameter controls whether or not the server will
# attempt to do a DNS update when a lease is confirmed. We default to the
# behavior of the version 2 packages ('none', since DHCP v2 didn't
# have support for DDNS.)
ddns-update-style none;

# option definitions common to all supported networks...
#option domain-name "example.org";
#option domain-name-servers ns1.example.org, ns2.example.org;

default-lease-time 600;
max-lease-time 7200;

# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
authoritative;

# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
log-facility local7;

# No service will be given on this subnet, but declaring it helps the
# DHCP server to understand the network topology.

#subnet 10.152.187.0 netmask 255.255.255.0 {
#}

# This is a very basic subnet declaration.

#subnet 10.254.239.0 netmask 255.255.255.224 {
#  range 10.254.239.10 10.254.239.20;
#  option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
#}

# This declaration allows BOOTP clients to get dynamic addresses,
# which we don't really recommend.

#subnet 10.254.239.32 netmask 255.255.255.224 {
#  range dynamic-bootp 10.254.239.40 10.254.239.60;
#  option broadcast-address 10.254.239.31;
#  option routers rtr-239-32-1.example.org;
#}

# A slightly different configuration for an internal subnet.
#subnet 10.5.5.0 netmask 255.255.255.224 {
#  range 10.5.5.26 10.5.5.30;
#  option domain-name-servers ns1.internal.example.org;
#  option domain-name "internal.example.org";
#  option routers 10.5.5.1;
#  option broadcast-address 10.5.5.31;
#  default-lease-time 600;
#  max-lease-time 7200;
#}

# Hosts which require special configuration options can be listed in
# host statements.   If no address is specified, the address will be
# allocated dynamically (if possible), but the host-specific information
# will still come from the host declaration.

#host passacaglia {
#  hardware ethernet 0:0:c0:5d:bd:95;
#  filename "vmunix.passacaglia";
#  server-name "toccata.fugue.com";
#}

# Fixed IP addresses can also be specified for hosts.   These addresses
# should not also be listed as being available for dynamic assignment.
# Hosts for which fixed IP addresses have been specified can boot using
# BOOTP or DHCP.   Hosts for which no fixed address is specified can only
# be booted with DHCP, unless there is an address range on the subnet
# to which a BOOTP client is connected which has the dynamic-bootp flag
# set.
#host fantasia {
#  hardware ethernet 08:00:07:26:c0:a5;
#  fixed-address fantasia.fugue.com;
#}

# You can declare a class of clients and then do address allocation
# based on that.   The example below shows a case where all clients
# in a certain class get addresses on the 10.17.224/24 subnet, and all
# other clients get addresses on the 10.0.29/24 subnet.

#class "foo" {
#  match if substring (option vendor-class-identifier, 0, 4) = "SUNW";
#}

#shared-network 224-29 {
#  subnet 10.17.224.0 netmask 255.255.255.0 {
#    option routers rtr-224.example.org;
#  }
#  subnet 10.0.29.0 netmask 255.255.255.0 {
#    option routers rtr-29.example.org;
#  }
#  pool {
#    allow members of "foo";
#    range 10.17.224.10 10.17.224.250;
#  }
#  pool {
#    deny members of "foo";
#    range 10.0.29.10 10.0.29.230;
#  }
#}

subnet 10.10.0.0 netmask 255.255.255.0 {
	range 10.10.0.25 10.10.0.50;
	option domain-name-servers 8.8.4.4;
	option routers 10.10.0.1;
	#interface wlan0;
	option domain-name "local";
}

/etc/default/isc-dhcp-server

# Defaults for isc-dhcp-server initscript
# sourced by /etc/init.d/isc-dhcp-server
# installed at /etc/default/isc-dhcp-server by the maintainer scripts

#
# This is a POSIX shell fragment
#

# Path to dhcpd's config file (default: /etc/dhcp/dhcpd.conf).
DHCPD_CONF=/etc/dhcp/dhcpd.conf

# Path to dhcpd's PID file (default: /var/run/dhcpd.pid).
DHCPD_PID=/var/run/dhcpd.pid

# Additional options to start dhcpd with.
#	Don't use options -cf or -pf here; use DHCPD_CONF/ DHCPD_PID instead
#OPTIONS=""

# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
#	Separate multiple interfaces with spaces, e.g. "eth0 eth1".
INTERFACES="wlan0"

/etc/network/interfaces

auto lo

iface lo inet loopback
iface eth0 inet dhcp

auto wlan0
#allow-hotplug wlan0
iface wlan0 inet static
address 10.10.0.1
netmask 255.255.255.0

#iface default inet dhcp

up iptables-restore < /etc/iptables.ipv4.nat

After setting up Pi as an AP, I tested the Camera Module. Installing it is pretty simple: take the camera ribbon pin and insert it in Pi’s Serial Interface with the silver pins facing the HDMI port. In the terminal running SSH connection to the Pi, type sudo raspi-config. Enable Camera in the options. Now take a still image with raspistill -o img.jpg

You can check the still by copying the still to your computer over ssh using scp pi@[pi's IP address]:/home/pi/img.jpg Downloads

Time to setup recording. We are going to use the built-in python picamera module to capture 720p raw footage and convert it to mp4. The script cam.py is given below. For the conversion we need to install gpac.

sudo apt-get install gpac

Our script records raw footage in h264 codec. We need to convert it into something that most players can play such as mp4. Here’s command to convert .h264 into .mp4:

MP4Box -add output.h264 output.mp4

Zabardast! Now we have a playable mp4 file.

Now we need to automate this process so that it starts on its own without need for manual interaction. We need the footage to be converted and stored in a folder structure. And finally we need to be removing footage after 7 days so that our Hard Drive does not fill up with old footage.

For all that we are going to use scripts. We are going to write a script in python that stores footage for 17 hours in one hour increments.

cam.py

#!/usr/bin/env python

# cam.py

import os
import picamera
import subprocess

with picamera.PiCamera() as camera:
    camera.resolution = (1920, 1080)
    camera.framerate = 25
    camera.vflip = True

    for filename in camera.record_sequence(['/media/toshiba/motion/hour%02d.h264' % (h + 1) for h in range(17)], quality=20, bitrate=6000000):
        camera.wait_recording(60 * 60)

And another script that encodes our raw footage into mp4 files and removes the raw footage.

encode.py

#!/usr/bin/env python

#encode.py

import os
import subprocess

for item in os.listdir('/media/toshiba/raw'):
        fname = item[:-5]
        try:
            subprocess.call("MP4Box -add /media/toshiba/raw/%s, /media/toshiba/video/%s.mp4" %(item, fname), shell=True)
        except Exception as e:
            print e
            break
        os.remove('/media/toshiba/motion/%s' %item)

Now we are going to set a cron job that runs recording script at 6am and encoding script at 11pm. To edit cron, we need the command crontab -e. Here’s my cron file:

0 6 * * * /usr/bin/python /home/pi/Code/cam.py
0 23 * * * /usr/bin/python /home/pi/Code/encode.py
@daily find /media/toshiba/Footage -type f -mtime +7 -delete
@daily find /media/toshiba/Footage -type d -empty -delete

We have the recording script running from 6am to 11pm and the encoding script running after 11pm. We have another entry that daily deletes files older than 7 days and another one that deletes folders that are empty.

One last thing to do is disable the camera LED, because the light reflects in our screen covering the casing.

Add disable_led_camera=1 to /boot/config.txt

Now that we have setup the Pi to our liking, lets assemble our equipment together.

We cut the plastic sheet to the size of our plate to provide insulation between the Hard Drive, the plate and the USB Hub underneath and fixed it to both sides of the plate with rubber bands. Then we cut the reflective paper into the shape of the front end and pasted it in front of the plate with UHU.

20150120_181418

Next we placed the Hard Drive on the sheet and anchored it rubber bands as well.

20150120_181627

Then we screwed our Raspberry Pi with long screws onto the plate and tightened the nuts at the other end.

20150120_182552 20150120_182611

Next we placed the USB Hub on the floor of the camera casing and anchored it with a rubber band passing through one of the vents. Since our plate was going to cover most of the Hub, we needed to make sure that we had at least two USB ports visible. We also had to make a hole in the back of the casing to let the charger for the Hub through because there wasn’t enough room in the casing for it to come through the main opening and fit into the charging port. It ended up nice and tight! We plugged in the Hub’s USB cable into the Pi.

20150120_183815 20150120_184612

Then we plugged in the USB WiFi Adapter in the USB Hub along with the Hard Drive’s USB cable. We snaked the power cord to the Pi alongside of the Hard Drive and powered it ON! The Pi has no switch, you just plug in the adapter and it turns on and starts booting. Looks beautiful!

20150120_202152

Thats it folks! Your own Raspberry Pi based Security Cam that stores 720p HD footage for a week and acts as its own WiFi hostspot (so you don’t need any LAN cable or extra router to connect to it). It has only 1 wire for Power coming out of it and its power requirements are so low that if you connect it to a UPS, it will not put any load on your battery and run 24/7. You can add a PTCL EVO Wingle to give it internet access and use the internet by connecting to its Wifi network or watch the footage stored from a remote location by running a Samba Server on it (which we are running). The possibilities are endless 🙂

2015-01-23 11.38.02

Elementary OS + conky

Elementary OS

PTCL DSL Broadband Upload Speed

I wanted to find if upload speed changed with PTCL Broadband packages but couldn’t find it on their website. So I called the helpline. Turns out, it doesn’t matter what package you have: 4mbps or 12mbps…You get a fixed 128KBps upload link. Just putting it here for posterity.

Using vnstat with LAN-over-USB Devices

There are many tools on linux to monitor your bandwidth usage but one that I prefer is vnstat, since I can never be bothered to learn the intricacies of wireshark. An excellent introduction to vnstat and its command line parameters can be read here.

Internet Service Providers have been introducing many devices that are portable and connect to the computer via USB. These devices act as plug and play modems establishing LAN over USB interface. Their appeal includes that they can stay powered on through the USB interface without any need for a separate power adapter. I bought one such device for a trip from one of the local ISPs in Pakistan, Wi-Tribe. Their pocket USB Modem is a handy LAN-over-USB device that works out of the box in Windows, Mac and Linux which is pretty sweet since I use Ubuntu. The device appears as a Wired Connection in Ubuntu and is treated like a regular LAN connection. Connection Information in Ubuntu’s Network Applet shows it as eth1 using the cdc_ether driver.

My data plan was 1Mbps/30GB per month and I wanted to make sure I stayed under the limit. I fired up the terminal and typed in:

vnstat -i eth1

This resulted in Error: Unable to read database "/var/lib/vnstat/eth1".

Thats weird. A quick ls /var/lib/vnstat/ showed no eth1 interface present in vnstat. I updated the vnstat database with sudo vnstat -u which created a new database for interface eth1. I typed vnstat -i eth1 again after a few minutes but this time it said: "eth1: not enough data available yet". Fine, I kept using the internet and tried again after 15 minutes. The same error happened again.

I typed vnstat -l -i eth1 to see live capture of data across eth1. It showed me it was indeed capturing data across eth1 but for some reason some hundred megabytes worth of data was not enough for vnstat. After a few futile tries I decided to let go. The next day when I tried it again, it was working! Bingo.

I added an entry to my conky: ${execpi 1800  vnstat -i eth1 -m | grep "`date +"%b '%y"`" | awk '{print $1, $3 $4}'} which checks incoming data for the current month every two hours and allows me to keep an eye on my data usage.

multi_tweet.py – Send a single tweet to multiple people that you follow

I wrote this piece of code to send a single tweet to multiple people that I follow. Its mildly annoying to copy paste the same tweet and add twitter handles to it. I wanted to see if I could automate that using python. This code uses the tweepy module which, on Ubuntu, you can get by typing sudo apt-get install tweepy.

#!/usr/bin/env python

# multi_tweet_all.py

import tweepy
import time
import sys

version = "1.5"

# Consumer keys and access tokens, used for OAuth
consumer_key = "" #ENTER YOUR DETAILS IN BETWEEN QUOTES
consumer_secret = ""
access_token = ""
access_token_secret = ""

# OAuth process, using the keys and tokens
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

# Creation of the actual interface, using authentication
api = tweepy.API(auth)

## Check reset time
x = 0
rls = api.rate_limit_status()
rem = rls['resources']['friends']['/friends/list']['remaining']
##print rem
if rem == 0:
    print 'Error: Rate limit exceeded!!\nwaiting 60s...'
    time.sleep(60)
    x = x+1

MAX_LENGTH = 140

class TweetTooLongError(Exception):
    """
    Raised when a user would be too long to add to the tweet, even alone.
    """
    pass

def generate_tweets(message, users):
    """
    Generate tweets based around a message, with users
    appended to each tweet.

    :param message: the base message
    :param users: a group of users to append
    :returns: tweets based around the message to the users
    """

    add = ""
    longest_in_list = ' @' + max(users, key=len)

    if len(longest_in_list) + len(message) > MAX_LENGTH:
        raise TweetTooLongError(
            "At least one user would make the tweet too long."
        )

    while users:
        new_message = message
        while users and len(new_message) + len(add) <= MAX_LENGTH:
            add = ' @' + users.pop(0)
            new_message += add
        api.update_status(new_message)
        time.sleep(5)
        print new_message

def main():
    running = True

    while running:
        twitter_handle = raw_input('Enter twitter handle w/o "@": ')
        tweet = raw_input('Enter tweet: ')

        if len(tweet) > MAX_LENGTH:
            print 'Over 140 chars, not allowed'
        else:
            running = False

    conv_dict = {'two' : '2', 'we' : 'v', 'for': '4', 'about' : 'abt', 'some': 'sum', 'are' : 'r', 'people' : 'ppl', 'to' : '2', 'and' : '&'}

    for key in conv_dict:
        if key in tweet:
            tweet = tweet.replace(key, conv_dict[key])

    tweet = tweet.replace(', ', ',')
    tl = len(tweet)

    print tl, 'chars\n'

    all_lst = []

    for user in tweepy.Cursor(api.friends, screen_name=twitter_handle).items():
        #print user.screen_name
        all_lst.append(user.screen_name)

    all_lst = sorted(all_lst)

    selected_lst = []
    num_lst = []
    not_lst = []

    for i in xrange(len(all_lst)//2):
        first = '(%s) %s' % (i, all_lst[i])
        padding = ' ' * (30 - len(first))
        second = '(%s) %s' % (i + len(all_lst)//2, all_lst[i + len(all_lst)//2])
        print '%s%s%s' % (first, padding, second)

    choice = raw_input('\nEnter nums of users (csv) [e.g. 5-8,19,21] or "all" or "not(csv)": ')

    if choice.startswith('not'):
        choice = choice[4:len(choice)-1]
        for string in choice.split(','):
            if '-' in string:
                lst = string.split('-')
                not_lst = num_lst + range(int(lst[0]), int(lst[1])+1)
            else:
                not_lst.append(int(string))
        s = set(range(1, len(all_lst)+1))
        num_lst = list(s - set(not_lst))

    else:
        for string in choice.split(','):
            if '-' in string:
                lst = string.split('-')
                num_lst = num_lst + range(int(lst[0]), int(lst[1])+1)
            elif string == 'all':
                num_lst = range(1, len(all_lst)+1)
            else:
                num_lst.append(int(string))

    for num in num_lst:
        for pos, usr in enumerate(all_lst):
            if num == pos:
                selected_lst.append(usr)

    print '%d users selected\n' %len(selected_lst)
    generate_tweets(tweet, selected_lst)

if __name__ == '__main__':
    running = True

    while running:
        try:
            main()
            running = False

        except tweepy.error.TweepError as e:
            if e.reason.find('Rate limit exceeded') != -1 and x == 0:
                print 'Error: Rate limit exceeded!!\nwaiting 60s...'
                time.sleep(60)
                x = x+1

            elif e.reason.find('Rate limit exceeded') != -1 and x > 0:
                print 'Limit still exceeded.\nwaiting another 60s... (%ss since limit reached)' %(colored(x*60, 'red'))
                time.sleep(60)
                x = x+1

            else:
                print 'Error: '+str(e.reason)
                running = False

        except KeyboardInterrupt:
            print 'Exiting...'
            time.sleep(3)
            sys.exit()

You need to enter four keys in the program code: consumer_key, consumer_secret, access_token, access_token_secret. You can get them by going here.

The program will get your username and tweet as input, print a list of people you follow and ask you to select people you want to send that tweet to. It will then add those handles to the tweet automatically keeping the tweet within 140 characters. It will send that tweet multiple times on your behalf until all the people you selected have been added to the tweet. Its intelligent enough to add multiple handles to the tweet until the 140 character limit is reached rather than sending the same tweet 20 times with one handle attached each time.

uptime_log.py – Monitor server uptime stats

Load shedding in Pakistan means that eventually your servers will crash and you’ll have to restart them. I wrote a little python script to log how long my server stays up (So far on UPS, the record has been 35 days…Its pretty pathetic I know). The script updates uptime every 2 hours in a text file until server reboots and then starts a new entry.

#!/usr/bin/env python

# uptime_log.py

from datetime import timedelta, datetime
import fileinput
from os import path

d = datetime.today()
today = d.strftime('%d/%m/%y')

with open('/proc/uptime', 'r') as f:
    uptime_seconds = round(float(f.readline().split()[0]))
    delta = timedelta(seconds = uptime_seconds)
    current_timestamp = [str(delta.days), str(delta.seconds/3600), str((delta.seconds%3600)/60), str((delta.seconds%3600)%60)]
    uptime_string = ':'.join(current_timestamp)

if path.exists('/home/saad/uptime_log'):
	uptime_log = open('/home/saad/uptime_log', 'a+')
	all_lines = uptime_log.readlines()
	last_line = all_lines[len(all_lines)-1]
	stored_timestamp = (last_line[:last_line.rindex(' ')].replace('\n', '')).split(':')
	stored_val = timedelta(days=int(stored_timestamp[0]), hours=int(stored_timestamp[1]), minutes=int(stored_timestamp[2]), seconds=int(stored_timestamp[3]))
	current_val = delta
	
	if current_val > stored_val:
		for line in fileinput.input('/home/saad/uptime_log', inplace = 1):
			print line.replace(last_line, uptime_string + ' (' + today + ')\n'),
		fileinput.close()
	
	else:
		uptime_log.write(uptime_string + ' (' + today + ')\n')
	
	uptime_log.close()

else:
	uptime_log = open('/home/saad/uptime_log', 'w')
	uptime_log.write(uptime_string + ' (' + today + ')\n')
	uptime_log.close()

This produces a log file in your home directory (remember to replace ‘/home/saad’ with whatever your home directory is called). My log file looks like:

0:22:07:26 (25/07/13)
0:23:05:53 (26/07/13)

This shows that my server had been up for 0 days, 22 hours, 7 mins on 25th July until I rebooted the machine. Now it has been up for 23 hours, 5 mins.

PTCL Smart TV Jadoo Plus: First Impressions

A slim PTCL exchange employee, wearing a cap and worn out jeans, handed me a small box. “Too small”, was the first thing that popped in my mind as I took the PTCL Smart TV Jadoo Plus box. It is a tiny device indeed, smaller than my PTCL router/modem. Big things come in small packages…I guess.

The charges of the my PTCL Smart TV Jadoo Plus package are:

  1. Smart TV Device Charges: 10,000rs upfront or 600rs per month for 2 years (makes the device cost 14,400rs)
  2. Smart TV Subscription Charges: 600rs per month
  3. DSL charges 4Mbps: 2,100rs per month (You need 4Mbps if you want both Internet and Smart TV to work smoothly although it will work with a 2Mbps connection)

Thats a total of 3,300rs per month if you opt to pay for the device in installments.

The package contains the jadoo box, a HDMI cable, a composite video cable, a power adapter and a remote.

Jadoo-TV-3-Plus

The jadoo box needs to be connected to the PTCL modem with ethernet cable. The placement was a bit of an issue for me as my modem is in the basement, while my Plasma TV is upstairs. The length of the HDMI cable and the power adapter means that you cannot place the device more than an arm’s length from the TV. So I had to run a long ethernet cable all the way from the basement to the device.

jadoo plus

I powered on the device and in the main menu, opened Settings > Setup Wizard. There are self-explanatory steps which take you through Language selection, Time/Date, Weather and Network Settings. I had to modify my Network Settings due to my unconventional setup but you can stick with the defaults.

The main menu had an option for Apps installation which I didn’t explore thoroughly but a cursory tour showed many exciting possibilities including Youtube, Facebook, Twitter, Flicker and many more. I went straight to the Jadoo Plus option and selected Live TV. This opened a Channel list sorted into groups:

NEWS

  1. ARY News
  2. Dunya News
  3. Dawn News
  4. Samaa
  5. PTV World
  6. CCTV News
  7. Al Jazeera English
  8. RT News
  9. Geo News
  10. Express News
  11. Aaj News
  12. CNBC Pakistan
  13. PTV News
  14. News One
  15. Waqt News
  16. BBC News
  17. Fox News
  18. Sky News
  19. Al Jazeera Arabic
  20. Capital TV
  21. CNN
  22. Geo Tez

ENTERTAINMENT

  1. Zam TV
  2. Filmazia
  3. Indus Vision
  4. Lights
  5. ATV
  6. TV One
  7. AXN
  8. Koh-e-Noor
  9. PTV Home
  10. Express Entertainment
  11. Star World
  12. Urdu One
  13. Geo
  14. Hum TV
  15. Filmax
  16. HBO HD Hits
  17. Cinemax
  18. WB TV
  19. HBO
  20. Star Movies
  21. DIVA Universal
  22. Vibe TV
  23. ARY Digital
  24. Hum 2
  25. A-Plus Entertainment
  26. Silverscreen
  27. Film World
  28. Geo Kahani

SPORTS

  1. TEN Sports
  2. Abu Dhabi Al-Riazia
  3. TEN Golf
  4. Star Cricket
  5. Star Sports
  6. PTV Sports
  7. ESPN
  8. Geo Super

INFOTAINMENT

  1. Health TV
  2. BBC Entertainment
  3. History Channel
  4. FOX Crime
  5. NAT Geo Wild
  6. NAT Geo Adventure
  7. Animal Planet
  8. Discovery Channel
  9. NAT Geo
  10. Investigation Discovery
  11. Discovery World
  12. BBC Knowledge

CULINARY

  1. Zaiqa TV
  2. ARY Zauq
  3. Masala TV

IN HOUSE

  1. Awakening
  2. Smart Products
  3. Smart Melodies
  4. Smart Films
  5. Smart Life

KIDS

  1. Kidsco
  2. Al Itfal
  3. Animax
  4. Disnep Channel
  5. Baby TV
  6. Nickelodeon
  7. Cartoon Network
  8. Disnep XD
  9. BBC CBeebies

MUSIC

  1. Arabica TV
  2. Play TV
  3. Oxygene
  4. Aag TV
  5. Channel V
  6. ARY Musik

No MTV?? WTF!

REGIONAL

  1. Waseeb TV
  2. Sindh TV
  3. Sindh News
  4. Discovery Turbo
  5. Sohni Dharti
  6. Kashish
  7. Kay 2
  8. APNA Channel
  9. Khyber
  10. Channel 5
  11. Metro One
  12. Star TV Lite
  13. City 42

RELIGIOUS

  1. Peace TV English
  2. ARY QTV
  3. Peace TV Urdu

BUSINESS

  1. Bloomberg TV
  2. Business Plus

So that’s a total of 111 Channels (Not 125 as listed on the website!).

I watched a couple of news channels and some HBO. The channels are all in Standard Definition (SD 480p) which are upscaled to 1080p. After a few hours, I still have to find a channel that is broadcasted in High Definition (HD 720p or 1080p). The result is still better than the local cable I used to have.
2013-05-15 20.09.09
Naturally if you stand too close to the TV you can see the result of 480p image blown up to a whopping 1080p with pixellation and artifacts visible.
2013-05-15 20.06.17

None of the channels I tested had Electronic Program Guide (EPG). It is a feature that allows you to know the schedule of programs for the day. There are no Time Shifting (TSTV) capabilities, which means you can’t pause, rewind or forward television. That is for PTCL Smart TV only. The Video-On-Demand (VOD) content also doesn’t work, throwing “content not available” errors for all the clips I tried to play.

One really cool feature of the box is that if you have NAS or a Home Server on the network, you can access its contents and watch your stored movies and TV shows on the TV! There is also a USB port in front of the device where you can attach an external hard drive or USB and watch pictures or other content on the TV.

Well so far its been okay, though it’s a bummer when you have an HD capable TV with no HD content to watch. Don’t let the marketing fool you, so far I have not seen any HD channel in the list. I don’t think its PTCL’s fault though. Most TV stations still broadcast SD content.

تحریکِ انصاف پر حمائتیوں کی تنقید

tehreek

gcalcli_agenda.py – Google Calendar agenda in conky

Conky is a wonderful application for Linux users. All sorts of valuable information is printed right onto your wallpaper in almost any manner you want! I usually like to see my calendar agenda for the month in conky. However making conky interface with google calendar is way above my skill set… And this is why I love linux. 9 times out of 10 you will be able to find a program that does exactly what you want.

Enter gcalcli! Its a nifty application that allows you to access google calendar from the command line. It can create/delete tasks, produce reminders and a do a host of other stuff. All we are going to use it for is to get our agenda for the month. The version in Ubuntu repos is out of date so I’ll be using the latest version right from the source which supports OAuth. You’ll need to install some dependencies before you can run the program which can be obtained by reading the help file.

The first time you run the command in the terminal ([path to gcalcli]/gcalcli list), follow the instructions. You’ll authenticate with google and then you’re good to go. Now the program has a command line switch that allows you to display the output in colors in a syntax used in conky. But playing with it, I found it to have limited choices of color and the most outrageous default settings. The output also wasn’t properly aligned in conky.

So I decided to write a little program that took plain output from the program, colored the output according to my tastes and aligned it properly.

#!/usr/bin/python

# gcalcli_agenda.py

version = "3.0.1"

from datetime import date, timedelta, datetime
import subprocess
import textwrap
import re
import heapq

pattern_string = '(1[012]|[1-9]):[0-5][0-9](\\s)?(?i)(am|pm)'
pattern = re.compile(pattern_string)

def join_time(lst):
	mod_lst = []
	for number, item in enumerate(lst):
		if re.search(pattern, item):
			mod_lst.append(item + ' ' + lst[number+1])
			del lst[number+1]
		else:
			mod_lst.append(item)
	return mod_lst

def get_first_day(dt, d_years=0, d_months=0):
	# d_years, d_months are "deltas" to apply to dt
	y, m = dt.year + d_years, dt.month + d_months
	a, m = divmod(m-1, 12)
	return date(y+a, m+1, 1)

def get_last_day(dt):
	return get_first_day(dt, 0, 1) + timedelta(-1)

d = date.today()
e = datetime.today()
start_first = get_first_day(d).strftime('%Y-%m-%d') + 'T00:00'
start = d.strftime('%m/%d/%Y')
end = get_last_day(d).strftime('%Y-%m-%d') + 'T23:59'
today = d.strftime('%a %b %d')

try:
	proc = subprocess.check_output('/home/saad/Downloads/gcalcli-master/gcalcli --nocolor agenda %s %s' %(start_first, end), shell=True)

except subprocess.CalledProcessError:
	print 'There seems to be a problem...'

lst = proc.split('\n')
lst = filter(None, lst)
lst = [item.split('  ') for item in lst]
lst = [item for sublist in lst for item in sublist]
lst = filter(None, lst)

date_word = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')

dct = {}    

for item in lst:
    if any(item.startswith(d) for d in date_word):
        dct[item] = []
        saveItem = item
    else:
        dct[saveItem].append(item.strip())

def parse_date(datestring):
    return datetime.strptime(datestring, "%a %b %d")

deltas = []
val_len = []

for key in dct:
	val_len.append(len(''.join(item for item in dct[key])))
	
counter = 0
for eachLen in val_len:
	if eachLen > 37:
		counter = counter + 2
	else:
		counter = counter + 1

if counter > 5:
	n = counter - 5
	
	for key in dct:
		deltas.append(e - datetime.strptime(key, '%a %b %d'))
		
	for key in sorted(dct, key=parse_date):
		tdelta = e - datetime.strptime(key, '%a %b %d')
		if tdelta in heapq.nlargest(n, deltas):
			pass
		else:
			if key == today:
				value = dct[key]
				val1 = '${color green}' + key + '$color: ' 
				mod_val = join_time(value) 
				val2 = textwrap.wrap(', '.join(item for item in mod_val), 37)
				print val1 + '${color 40E0D0}' + '$color\n            ${color 40E0D0}'.join(item for item in val2) + '$color'
			else:
				value = dct[key]
				mod_val = join_time(value)
				output = key + ': ' + ', '.join(item for item in mod_val)
				print '\n            '.join(textwrap.wrap(output, 49))

else:	
	for key in sorted(dct, key=parse_date):
		if key == today:
			value = dct[key]
			val1 = '${color green}' + key + '$color: ' 
			mod_val = join_time(value) 
			val2 = textwrap.wrap(', '.join(item for item in mod_val), 37)
			print val1 + '${color 40E0D0}' + '$color\n            ${color 40E0D0}'.join(item for item in val2) + '$color'
		else:
			value = dct[key]
			mod_val = join_time(value)
			output = key + ': ' + ', '.join(item for item in mod_val)
			print '\n            '.join(textwrap.wrap(output, 49))

The program takes your agenda list for the current month and checks for any item on the current date, colorizes that item and outputs to conky with agenda list properly aligned.

conky Calendar Events

All you have to do is replace the path to gcalcli with location of the script in your computer, change the colors to your liking and make the program executable. Then put this line in your .conkyrc file: ${execpi 3600 [path to script]/gcalcli_agenda.py} and you’re good to go.

On the road to Naya Pakistan