Hyperion LED’s & Nagios (Part 2)

Maybe you read Part 1, maybe you skipped it ūüôĀ

Either way Part 2 is going to cover creating a new effect for Nagios. I wont say it’s the best bit of python programming I’ve done (Yes I copied and changed another effect as a starting point). The main thing I wanted was an alert status (yes Star Trek does come into it).

There’s 4 files to this effect:

  1. alert.py – The actual python program
  2. alert-green.json – etting a green colour (yes it’s spelt COLOUR not color!!!!!)
  3. alert-yellow.json
  4. alert-red.json

My hyperion is installed to ‘/opt/hyperion’ so first thing is to go to the effects directory

cd /opt/hyperion/effects

Then create the alert.py file

nano alert.py

Paste the following into it and save the file

import hyperion
import time
import colorsys
import numpy as np

# Get the rotation time
colorfrom =     hyperion.args.get('colorfrom', (0,0,0))
colorto =       hyperion.args.get('colorto', (0,255,0))
speed   =       float(hyperion.args.get('speed', 0.05))
freq    =       float(hyperion.args.get('frequency', 2.5))
step    =       float(hyperion.args.get('step', 30.0))
ledCount =      hyperion.ledCount

# Setup the colors
colorfromnp     =       np.array(colorfrom)
colortonp       =       np.array(colorto)

#define the lerp calc
def lerp(a, b, t):
        x = a*(1 - t) + b*t
        x = np.around(x)
        x = x.tolist()
        return x

# Start the write data loop
while not hyperion.abort():
        for i in range(0,int(step)):
                n = i / step
                thiscolor = lerp(colorfromnp, colortonp, n)
                colorLedsData = ledCount * bytearray((int(thiscolor[0]), int(thiscolor[1]), int(thiscolor[2])))
                hyperion.setColor(colorLedsData)
                time.sleep(speed)
        time.sleep(speed*10)
        for i in range(1,int(step)):
                n = i / step
                thiscolor = lerp(colortonp, colorfromnp, n)
                colorLedsData = ledCount * bytearray((int(thiscolor[0]), int(thiscolor[1]), int(thiscolor[2])))
                hyperion.setColor(colorLedsData)
                time.sleep(speed)
        colorLedsData = ledCount * bytearray(colorfrom)
        hyperion.setColor(colorLedsData)
        time.sleep(freq)

I do use a numpy array, I can’t remember installing it but may have done.¬†You can test if you have it installed

python
import numpy as np

Then we create the alert-green.json

nano alert-green.json

And Paste

{
        "name" : "Green Alert",
        "script" : "alert.py",
        "args" :
        {
                "colorfrom" : [0,0,0],
                "colorto" : [0,255,0],
                "speed" : 0.05,
                "freq" : 2.5,
                "step" : 30.0
        }
}

alert-yellow.json

{
        "name" : "Yellow Alert",
        "script" : "alert.py",
        "args" :
        {
                "colorfrom" : [0,0,0],
                "colorto" : [255,255,0],
                "speed" : 0.05,
                "freq" : 2.5,
                "step" : 30.0
        }
}

alert-red.json

{
        "name" : "Red Alert",
        "script" : "alert.py",
        "args" :
        {
                "colorfrom" : [0,0,0],
                "colorto" : [255,0,0],
                "speed" : 0.05,
                "freq" : 2.5,
                "step" : 30.0
        }
}

Finally restart hyperion to pickup the new effects

service hyperion restart

That’s it, you should now be able to run these effects. From the terminal:

hyperion-remote -e "Red Alert"

Should start the led’s glowing Red. If not I’d start with ‘hyperion-remote -l’ to check the effects are listed, then move on to working out what’s missing (see above about numpy array).

A few notes on this effect:

  • Originally I programmed it forced to black, but it made sense to change this to a colorfrom value¬†for future (if you want it green and fading to red for example).
  • The speed is how quickly to progress through the fade.
  • The step is how many¬†colours it goes through to get from the colorfrom to the colorto
  • Working out the speed and the step together are important for a nice fade (not jerky).
  • The Freq in how long it stays on black before running the fade again.

This completes the hyperion side of the setup. I now have this effect installed and running on 3 PI’s and yes when there’s a problem they ALL go to yellow/red alert

See Part 3 for the nagios setup

Hyperion LED’s & Nagios (Part 1)

Part 1 is more Background story on my use of WS2801 WS2812b and Hyperion with the Raspberry Pi. Skip to Part 2 for the techy bit.

I’ve been using Hyperion for a while. I setup light behind the TV first off (using sticky tape) WS2801 and RaspBMC. This work brilliantly and I loved it. Spent hours tweaking the config so the LED’s were picking up the correct colour to¬†the screen.

With all that working and a length of LED’s left over I decided to run some up the stairs. They sit just under the banister lip so you can’t see them, just the light on the stairs. I set these to Rainbow swirl and let it. They’re been running for months, occasionally changing the effect to show off what they can do.

Then disaster struck, the power adapter stopped working. Have to be honest I didn’t really notice until I was going to bed at 2am and almost fell over. They’ve been there¬†giving off light (possibly a bit bright if anything) and I just got used to being able to see in the middle of the night without any other lights.

Anyway I digress, ordering a new power adapter I went searching for more LED’s (yes you can’t have enough of them once you’ve been playing). I decided that I’d really like to run some in my bedroom, the effects are cool and there would be plenty of light I wont need to use the main light with them on.

So I¬†looking at where I originally bought my WS2801’s and nothing ūüôĀ so off to google, the obvious thing was I was going to have a hard time sourcing them in the UK. but why they’re great. So off I went to the hyperion git site for info and found there’s newer versions WS2811 and WS2812b. ah that may help, another search and I found someone selling a load on ebay. So I bought¬†all he had 3xreels of WS1812b’s.

They turned up and I connected them up to try them as directed by hyperion. It was at this point I read the important bits RPi2 isn’t working yet and there maybe a problem with the PI communicating with them due to the voltage. I really thought I was going to have to make another little circuit to (buffer?) get them working. As a last ditched attempt it was mentioned try removing the resistor and try running them direct from python. I did both at the same time (not the best decision for troubleshooting. But to my surprise they worked.

So I killed the python program and restart hyperion, yep they’re working.

So off I went to stick them to the ceiling (they have sticky tape on the back). Done. If only I’d thought about connecting them before I stuck them up. I now had to work up in the air joining the cables. Not to worry I’ve done worse.

So I go to get what I need, by the time I got back up they’ve come down ūüôĀ bloody gravity! Now you’d think at this point I’d connect them up and sort out attaching them later Nope! (didn’t even enter my head) I was now on track to get them to stay up. Enter ‘SuperGlue’, applied¬†little dots along the strip and stuck them up (yes I glued my fingers to the ceiling too). Finally they’re up and staying there. Oh I should have connected them when they were down!

‘Bugger it, where’s my screw driver’ I connected them up, put a power connector on the end and powered them and the PI.

Then installed hyperion on yet another pi. and it all worked like magic.

Have a look at the video, there’s no light other than the TV and it’s dark outside, but the room is really bright.

[youtube=https://www.youtube.com/watch?v=khfJW3vXcCE]

Click here for Part 2

Weewx+Raspberry PI+HDMI+PyGame

I’ve been¬†using wview with my WH1080 weather station for some time (actually 2 of them). My main setup has been using my server, and every now and again the WH1080 would seem to lock up and nothing could get data out of it. The solution was to¬†drop it’s power, on reboot it would all start working again.

However wview also seemed to introduce lockups of it’s own and the only solution there was to reboot the server (not ideal). So when it came to setting up a second weather station¬†(in a remote location) I needed something a bit more stable and started looking at alternatives. I was doing this on a Raspberry PI and found wview. After installing it sometime last year it seemed pretty stable (although¬†the WH1080 still manages to lockup).

Back to my house and I’ve finally had enough of missing¬†weather data. One thing I really liked with wview was the ability to pull the archive data if the weather station had been running when the machine hadn’t (providing the USB hadn’t locked up), I really recommend looking at wview if your starting out.

I’ve already¬†covered setting up weewx on a Raspberry PI¬†and I’m not going to post about my exact configuration here. Instead I’m going to share my Python code for displaying the various graphs and gauges straight to the TV. At the moment I have my weather PI connected to the TV via HDMI. This may change later and then I’ll have to adjust the code to pull the images to another pi before displaying them (I have a similar project for displaying webcams already).

So a few things before the code.

  1. It’s my first real attempt at using classes, so my code will be more than a little scattered.
  2. You need to have python, pygame, wview, (and for this exact code Bootstrap for wview but you could just change the file paths to the Standard guages and graphs), mysql and wview configured for mysql.
  3. I have this started using an init script (added below).
  4. This runs the python program as root, I need to find a way to run this as a normal user (but that will affect point 5&6).
  5. This program checks mysql is able to be connected to and restarts mysql of not.
  6. It also checks the freshness of the index.html file (not the best way but a quick way) to make sure wview is running keeping the files upto date. If not it reboot the PI, this causes the weather station to reboot so if the USB locks up the whole system resets fixing it.

So now onto the python code

#!/usr/bin/python
import os
import time
import pygame
import MySQLdb as mdb
import signal
import sys

imglocation = "/var/www/weewx/Bootstrap"

class pyscreen :
   screen = None;

   def __init__(self):
       "Initializes a new pygame screen using the framebuffer"
       disp_no = os.getenv("DISPLAY")
       if disp_no:
           print "I'm running under X display = {0}".format(disp_no)

       # Check which framebuffer drivers are available.
       drivers = ['fbcon', 'directfb', 'svgalib']
       found = False
       for driver in drivers:
           # Make sure that SDL_VIDEODRIVER is set
           if not os.getenv('SDL_VIDEODRIVER'):
               os.putenv('SDL_VIDEODRIVER', driver)
           try:
               pygame.display.init()
           except pygame.error:
               print 'Driver: {0} failed.'.format(driver)
               continue
           found = True
           break

       if not found:
           raise Exception('No suitable video driver found!')

       size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
       print "Framebuffer size: %d x %d" % (size[0], size[1])
       self.screen = pygame.display.set_mode(size, pygame.FULLSCREEN)
       pygame.mouse.set_visible(False)
       # Clear the screen to start
       self.screen.fill((0, 0, 0))
       # Initialise font support
       pygame.font.init()
       # Render the screen
       pygame.display.update()

   def __del__(self):
       "Destructor to make sure pygame shuts down, etc."

   def size(self):
       size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
       return size

   def fill(self, colour):
       if self.screen.get_at((0,0)) != colour:
           self.screen.fill((0, 0, 0))
           self.screen.fill(colour)
           pygame.display.update()

   def image(self, img, locX, locY, sizX, sizY):
       try:
           if (( "week" not in img) and (os.stat(img).st_mtime > time.time() - 600)):
               # 600 = 10 mins.
               image = pygame.image.load(img)
               image = pygame.transform.scale(image, (sizX, sizY))
               self.screen.blit(image, (locX, locY))
           elif(( "week" in img) and (os.stat(img).st_mtime > time.time() - 7200)):
               # 7200 = 2 hours.
               image = pygame.image.load(img)
               image = pygame.transform.scale(image, (sizX, sizY))
               self.screen.blit(image, (locX, locY))
           else:
               pygame.draw.rect(self.screen, (255, 0, 0), (locX, locY, sizX, sizY), 0)
       except pygame.error, message:
           pygame.draw.rect(self.screen, (255, 0, 0), (locX, locY, sizX, sizY), 0)
       pygame.display.update()

   def text_object(self, msg, font):
       black = (0, 0, 0)
       textSurface = font.render(msg, True, black)
       return textSurface, textSurface.get_rect()

   def error(self, msg):
       largeText = pygame.font.Font('freesansbold.ttf', 115)
       TextSurf, TextRect = screeny.text_object(msg, largeText)
       size = screeny.size
       TextRect.center = ((pygame.display.Info().current_w/2),(pygame.display.Info().current_h/2))
       self.screen.blit(TextSurf, TextRect)

       pygame.display.update()

class fileman:
   global imglocation

   def __init__(self):
       "Init for fileman. Nothing to do atm."

   def __del__(self):
       "Destructor for fileman. Nothing to do again."

   def total_files(self):
       count=0
       for file in os.listdir(imglocation):
           if file.endswith(".png"):
               count=count+1
       return count

class sqly:
   def __init__(self):
       "do nothing"

   def test(self):
       try:
           con = mdb.connect('localhost', 'weewx', 'weewx', 'weather')

           cur = con.cursor()
           cur.execute("SELECT VERSION()")
           ver = cur.fetchone()
           return 0
       except mdb.Error, e:
           return 1

       finally:
            "do nothing"
   def restart(self):
       #wait 30 secs and try the connection again.
       time.sleep(30)
       if not mysql_con.test():
           try:
               os.system("service mysql start")
           except:
               "do nothing"

def sigterm_handler(_signo, _stack_frame):
    "When sysvinit send the TERM signal, cleanup before exiting"
    print("[" + get_now() + "] received signal {}, exiting...".format(_signo))
    sys.exit(0)

signal.signal(signal.SIGTERM, sigterm_handler)

def reboot():
    "check if we've been reboot in the last 30 mins"
    uptimef = open("/proc/uptime", "r")
    uptimestr = uptimef.read()
    uptimelst = uptimestr.split()
    uptimef.close()

    if float(uptimelst[0]) < 1800:
        "We've reboot in the last 30 mins, ignoring"
    else:
        try:
            os.system("reboot")
        except:
            "do nothing"


if __name__ == "__main__":
    try:
        screeny = pyscreen()
        filey = fileman()
        screeny.fill((0, 0, 255))
        size = screeny.size()
        border = 7
        sizewquarter = ((size[0]-(border*5))/4)
        sizehthird = ((size[1]-(border*4))/3)

        print("Total files : %d" % (filey.total_files()))
        while 1:
            mysql_con = sqly()
            if mysql_con.test():
                screeny.fill((250, 0, 0))
                screeny.error("Database Offline. Restarting!")
                mysql_con.restart()
            elif (os.stat("/var/www/weewx/Bootstrap/index.html").st_mtime < time.time() - 600):
                screeny.fill((254, 0, 0))
                screeny.error("NOT Updating. Reboot Imminent!")
                reboot()
            else:
                screeny.fill((0, 0, 255))
                screeny.image("/var/www/weewx/Bootstrap/barometerGauge.png", (border*1), (border*1), sizewquarter, sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/outTempGauge.png", ((border*2)+(sizewquarter*1)), (border*1), sizewquarter, sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/windDirGauge.png", ((border*3)+(sizewquarter*2)), (border*1), sizewquarter, sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/windSpeedGauge.png", ((border*4)+(sizewquarter*3)), (border*1), sizewquarter, sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/big_images/weekbarometer-Bootstrap.png", (border*1), ((border*2)+(sizehthird*1)), ((sizewquarter*2)+(border*1)), sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/big_images/weektempchill-Bootstrap.png", (border*1), ((border*3)+(sizehthird*2)), ((sizewquarter*2)+(border*1)), sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/big_images/weekwinddir-Bootstrap.png", ((border*3)+(sizewquarter*2)), ((border*2)+(sizehthird*1)), ((sizewquarter*2)+(border*1)), sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/big_images/weekwind-Bootstrap.png", ((border*3)+(sizewquarter*2)), ((border*3)+(sizehthird*2)), ((sizewquarter*2)+(border*1)), sizehthird)
            time.sleep(1)
    except KeyboardInterrupt:
        "We've got an interupt"

and now the init.d code

#!/bin/sh
#
# init script for displayweewx
#

### BEGIN INIT INFO
# Provides:          displayweewx
# Required-Start:    $syslog $network
# Required-Stop:     $syslog $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: init script to display weewx charts via HDMI output
# Description:       The python script queries mysql and file ages, so does not rely on mysql as a backup way to kick it.
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NAME=displayweewx
DAEMON=/root/displayweewx/main.py
DAEMONARGS=""
PIDFILE=/var/run/$NAME.pid
LOGFILE=/var/log/$NAME.log

. /lib/lsb/init-functions

test -f $DAEMON || exit 0

case "$1" in
    start)
        start-stop-daemon --start --background \
            --pidfile $PIDFILE --make-pidfile --startas /bin/bash \
            -- -c "exec stdbuf -oL -eL $DAEMON $DAEMONARGS > $LOGFILE 2>&1"
        log_end_msg $?
        ;;
    stop)
        start-stop-daemon --stop --pidfile $PIDFILE
        log_end_msg $?
        rm -f $PIDFILE
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    status)
        start-stop-daemon --status --pidfile $PIDFILE
        log_end_msg $?
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 2
        ;;
esac

exit 0

After all of this we get the following on the TV

From far far away.
From far far away.

Todays WordPress Adventures

Well it had to happen at some point, today I had a nice email from DigitalOcean saying they’ve disabled networking on one of my servers. This was because it’s ip address had been reported to RBL’s by several other servers.

Looking at the logs they included I was beating the s**t out of others wp-admin login pages. Now I know I wasn’t doing it personally, it was the first time in a long time I was in bed early and this seemed to start at 2am.

Luckily I could access my Droplet using the Console page, so after¬†login I sat thinking ‘um…..’ where exactly do you start. The server normally has quite a bit of traffic so the logs are always cluttered. Needle in a haystack springs to mind.

I decided to run htop and see if the server was doing much without any traffic coming in. Oh yes /usr/bin/host is eating resources. So do I kill it or not. I decided not to at this point. Without networking I’m not doing anymore harm, and leaving it running may help find out what’s¬†calling it.

It was a good call. I can’t give details of everything I did, I spent¬†a hour hours checking through stuff. I do remember checking lsof and finding a link between a process id for host and a file within wpallimport’s uploads directory. So I had a look in there, followed by some further searching of google. 1 file in particular .sd0 seems to bring back results and this seems to be what’s caused it.

To get my server running again, I disabled the entire site within apache that was affected (luckily not a major site) and reboot the server. Once I was happy there were no cronjobs or anything calling on this script I mailed DigitalOcean and asked them to re-enable networking. They’re pretty speedy and within 15 mins had done it. A further reboot and my servers back online minus the one site I’ve disabled.

I expect the cleanup for this is going to take weeks of checking files, against backups while keeping as much as possible online.

I’m pretty confident I know what’s caused it, an out of date wordpress install with an out of date wpallimport install.¬†It really goes to show that you have to¬†check old stuff and keep it upto date.

The most annoying thing for me is that WordPress has a multisite option (which¬†I use on 2 installs) and this allows me to keep plugins and everything upto date easily of sub-sites that¬†are barely used. but it doesn’t extend to multiple domains which would really¬†allow wordpress to be used across¬†all my domains from one central console and then everything would be kept upto date in one go.

I know there’s a plugin for multisite domains, but I feel this is more of a hack of the wordpress system rather than¬†wordpress properly designed to function with this in mind. I don’t want to install it and encounter many more problems.

It’s very bad admin on my part not having kept this site upto date, I’ll be the first to admit that but it’s easy to forget about installs you don’t use regularly. There must be some kind of nagios plugin to alert me to out of date plugins/versions for wordpress so I’ll be looking for that later in the week ūüôā

WooCommerce New Bulk Action Part 2

Ok so in Part 1 I said I wasn’t going to use the email function so removed it from the code I used from¬†http://www.niepes.com/web/how-to-create-a-custom-order-status-in-woocommerce/

Clearly I need to think ahead a bit more. Over the last few¬†weeks I’ve had a ton of sales, and I’m¬†preparing everything to be ready to send them out as soon as I receive them from the¬†supplier. Unfortunately our supplier contact me¬†and advised there’s a few days delay on one product and about 10 days delay on another.

So I now need to mass mail people telling them.

I did look at using elasticemail to mass mail, but there’s a whole thing in their templates gearing towards marketing mails and opt out information. This isn’t a marketing mail it’s part of the transaction, I could live with giving it some opt out links. but what I really need is to be able to mail merge the order information into the mail.

So I quickly gave up on using elastcmail’s campaign function. I still use them as my mail relay and they’ve been very good, and support has been top notch for the few questions I’ve had.

Anyway, I thought back to the code for adding bulk actions and remembered there was an email function. So I checked out the code again, and yep it should be able to do what I need.

I decided to do things a little differently though.

First I create 2 new order status using JetPack (you can code this but it’s easier to just type and submit in jetpack) :
stock-emailed
stock-email-fail

I’m going to be bulk assigning every order I want emailed to stock-email, anything that fails (wp-mail does fail) will go to stock-email-fail. This is the bit that wasn’t in the orginal code.

I also want to pass the orderid and name into the mail function. I could just have the mail function go get them, but it already queries the email address in the change_order_status function so I may as well keep it there.

Here’s my new Code.

//added for woocommerce bulk actions.
add_action('admin_footer-edit.php', 'custom_bulk_admin_footer');
function custom_bulk_admin_footer() {
 global $post_type;
 if($post_type == 'shop_order') {
 ?>
 <script type="text/javascript">
 jQuery(document).ready(function() {
 //printing status
 jQuery('<option>').val('printing').text('<?php _e('Mark as Printing')?>').appendTo("select[name='action']");
 jQuery('<option>').val('printing').text('<?php _e('Mark as Printing')?>').appendTo("select[name='action2']");

 //stock status
 jQuery('<option>').val('stock').text('<?php _e('Mark as Awaiting Stock')?>').appendTo("select[name='action']");
 jQuery('<option>').val('stock').text('<?php _e('Mark as Awaiting Stock')?>').appendTo("select[name='action2']");

 //stock status
 jQuery('<option>').val('stock-emailed').text('<?php _e('Mark as Awaiting Stock Emailed')?>').appendTo("select[name='action']");
 jQuery('<option>').val('stock-emailed').text('<?php _e('Mark as Awaiting Stock Emailed')?>').appendTo("select[name='action2']");
 });
 </script>
 <?php
 }
}
add_action('load-edit.php', 'custom_bulk_action');
function custom_bulk_action() {
 global $typenow;
 $post_type = $typenow;

 if($post_type == 'shop_order') {
 $wp_list_table = _get_list_table('WP_Posts_List_Table');
 $action = $wp_list_table->current_action();
 $allowed_actions = array("stock","printing","stock-emailed");
 if(!in_array($action, $allowed_actions)) return;

 if(isset($_REQUEST['post'])) {
 $orderids = array_map('intval', $_REQUEST['post']);
 }

 switch($action) {
 case "printing":
 foreach( $orderids as $orderis ) {
 change_order_status($orderid, $action);
 }
 case "stock":
 foreach( $orderids as $orderid ) {
 change_order_status($orderid, $action);
 }
 case "stock-emailed":
 foreach( $orderids as $orderid ) {
 set_time_limit(0);
 change_order_status($orderid, $action);
 }
 break;
 default: return;
 }

 $sendback = admin_url( "edit.php?post_type=$post_type&success=1" );
 wp_redirect($sendback);
 exit();
 }
}
function change_order_status($orderid, $action) {
 $order = new WC_Order($orderid);
 if(($action=='printing') && ($order->status!='printing')) {
 $order->update_status('printing', '');
 }
 if(($action=='stock') && ($order->status!='stock')) {
 $order->update_status('stock', '');
 }
 if(($action=='stock-emailed') && ($order->status!='stock-emailed')) {
 $email = get_post_meta( $orderid, '_billing_email' )[0];
 $name = get_post_meta( $orderid, '_billing_first_name')[0];
 $name = ucwords($name);
 if (send_this($email,$orderid,$name)) {
 $order->update_status('stock-emailed', '');
 } else {
 $order->update_status('stock-email-fail', '');
 }
 }
}
add_action('admin_notices', 'custom_bulk_admin_notices');
function custom_bulk_admin_notices() {
 global $post_type, $pagenow;
 if( $post_type == 'shop_order' && isset($_GET['success']) ) {
 echo '<div class="updated"><p>The orders have been successfully update!</p></div>';
 }
}
function send_this($email,$orderid,$name) {

 $headers = 'From: Shop <[email protected]>' . "\r\n";
 $headers .= "MIME-Version: 1.0\r\n";
 $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n";

 $subject = 'Order Update';

 $message = '

<html>
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title>
 Order Update: #' . $orderid . '
 </title>
 </head>
 <body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0">
 <div style=" background-color: #f5f5f5; width:100%; -webkit-text-size-adjust:none !important; margin:0; padding: 70px 0 70px 0;">
 <table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
 <tbody>
 <tr>
 <td align="center" valign="top">
 <div id="template_header_image">
 </div>
 <table border="0" cellpadding="0" cellspacing="0" max-width="600" id="template_container" style=" box-shadow:0 0 0 3px rgba(0,0,0,0.025) !impo$
 <tbody>
 <tr>
 <td align="center" valign="top">
 <!-- Header -->
 <table border="0" cellpadding="0" cellspacing="0" width="100%" id="template_header" style=" background-color: #557da1; color: #ffffff;$
 <tbody>
 <tr>
 <td>
 <h1 style=" color: #ffffff; margin:0; padding: 28px 24px; text-shadow: 0 1px 0 #7797b4; display:block; font-family:Arial; font$
 Order: #' . $orderid . ' Update
 </h1>
 </td>
 </tr>
 </tbody>
 </table>
 <!-- End Header -->
 </td>
 </tr>
 <tr>
 <td align="center" valign="top">
 <!-- Body -->
 <table border="0" cellpadding="0" cellspacing="0" width="600" id="template_body">
 <tbody>
 <tr>
 <td valign="top" style=" background-color: #fdfdfd; border-radius:6px !important;">
 <!-- Content -->
 <table border="0" cellpadding="20" cellspacing="0" width="100%">
 <tbody>
 <tr>
 <td valign="top">
 <div style=" color: #737373; font-family:Arial; font-size:14px; line-height:150%; text-align:left;">
 <p>
 Hi ' . $name . ',
 <br>
 <br>
 Thank you 

';

 return wp_mail( $email, $subject, $message, $headers );
}

I haven’t include the full email above, just a snip so you can see where I put the orderid and name. The actual template I’ve used is one off woocommerce itself to keep the look of the emails the same as an order progress.

To get the mails flowing just select a bunch of orders and bulk change them to ‘Mark as Awaiting Stock Emailed’. This happily looped through 250 at a time on my system. After doing over 3,000 I only had 14 moved automatically to the failed¬†status, and rerunning them didn’t cause any problems.

The one issue I did run into was the name of the failed status, originally I had it set as stock-emailed-fail. But there’s a limit on the number of characters you can use and it came into the system as stock-emailed-fai. I managed to loose 3 orders in the first test batch, as they were now assigned a¬†status that didn’t exist. So I had to find them in the database and set them to the proper status. If you keep your slugs short, you shouldn’t see this issue. but make sure you also type the exactly the same in the code as they appear in jetpack.

I take no responsibility for any lost orders as a result of using this code.

WooCommerce New Bulk Action

A few months back I added some new order statuses in WooCommerce, then changed the code to be able to use these from the bulk actions menu. I then added them into the dashboard so I could get a quick overview of what I needed to do each day.

This was just a quick (maybe not so quick) edit of the¬†admin files. A while later I update WooCommerce and bang goes my edits (yes I can hear everyone shouting ‘should have made a child theme’). I did actually have a child theme running, but it was just a quick dirty way of getting it done at the time. I needed to concentrate on orders.

Anyway back to now, we’ve had a few thousand orders in a short space of time and once again I need my custom statuses from the bulk menu. This time I decided to do it right.

After searching and searching,¬†I couldn’t find anyone saying how to¬†add to the bulk menu, plenty of stuff about adding a status but you have to then edit each order to use it. Knowing I’ve done it before and didn’t take half the day doing so I kept searching. Eventually I found¬†http://www.niepes.com/web/how-to-create-a-custom-order-status-in-woocommerce/¬†and this is exactly what I needed.

Well sort of. I didn’t want the email side of it and actually wanted to add a few customs (2 atm) . So I changed the code a little.

Find below the code I have now added to functions.php (note I already created the custom statuses of ‘printing’ and ‘stock’ using WooCommerce Jetpack.

//added for woocommerce bulk actions.
add_action('admin_footer-edit.php', 'custom_bulk_admin_footer');
function custom_bulk_admin_footer() {
 global $post_type;
 if($post_type == 'shop_order') {
 ?>
 <script type="text/javascript">
 jQuery(document).ready(function() {
 //printing status
 jQuery('<option>').val('printing').text('<?php _e('Mark as Printing')?>').appendTo("select[name='action']");
 jQuery('<option>').val('printing').text('<?php _e('Mark as Printing')?>').appendTo("select[name='action2']");
//stock status
 jQuery('<option>').val('stock').text('<?php _e('Mark as Awaiting Stock')?>').appendTo("select[name='action']");
 jQuery('<option>').val('stock').text('<?php _e('Mark as Awaiting Stock')?>').appendTo("select[name='action2']");
 });
 </script>
 <?php
 }
}
add_action('load-edit.php', 'custom_bulk_action');
function custom_bulk_action() {
 global $typenow;
 $post_type = $typenow;
if($post_type == 'shop_order') {
 $wp_list_table = _get_list_table('WP_Posts_List_Table');
 $action = $wp_list_table->current_action();
 $allowed_actions = array("stock","printing");
 if(!in_array($action, $allowed_actions)) return;
if(isset($_REQUEST['post'])) {
 $orderids = array_map('intval', $_REQUEST['post']);
 }
switch($action) {
 case "printing":
 foreach( $orderids as $orderis ) {
 change_order_status($orderid, $action);
 }
 case "stock":
 foreach( $orderids as $orderid ) {
 change_order_status($orderid, $action);
 }
 break;
 default: return;
 }
$sendback = admin_url( "edit.php?post_type=$post_type&success=1" );
 wp_redirect($sendback);
 exit();
 }
}
function change_order_status($orderid, $action) {
 $order = new WC_Order($orderid);
 if(($action=='printing') && ($order->status!='printing')) {
 $order->update_status('printing', '');
 }
 if(($action=='stock') && ($order->status!='stock')) {
 $order->update_status('stock', '');
 }
}
add_action('admin_notices', 'custom_bulk_admin_notices');
function custom_bulk_admin_notices() {
 global $post_type, $pagenow;
 if( $post_type == 'shop_order' && isset($_GET['success']) ) {
 echo '<div class="updated"><p>The orders have been successfully update!</p></div>';
 }
}

Checkout Part 2

Just can’t find it.

Another rambling entry.

I started this bog with the idea of keeping notes of things I may need to refer back to. Tonight I’ve spent 30 mins searching the entries for LDAP authentication on the PI. I do LDAP Auth on all my servers against 2 LDAP servers, and I’m sure I posted somewhere what I did to configure the PI’s to auth against LDAP but I just can’t find it.

What it has highlight though is 1) I don’t post nearly enough of what I do, and when I do it’s normally after I’ve done it and then write it up from memory. 2) The blog design just ins’t very user friendly.

So I’m going to try to write things up a bit more often as I’m doing them and tonight I’m going to look at some better designs. I’ve been installing wordpress quite a bit recently and it just seems to have themes and a better layout, so I’d like to get a similar design running on this blog. I would just run the blog on my own server, but I think there’s a good chance moving my blog will screw up any references to blogspot. Maybe something to look at though when I’m bored.

Raspbmc Hyperion On/Off

I’ve just finished making the surround for my TV after testing it all with sticky tape ūüôā it’s looks awesome.

But the first question I was asked “How do I turn it off?”, Apparently the answer of “You don’t” wasn’t the right one. So a little searching brought me to a fellow blog post http://blog.nadnerb.co.uk/?p=11 which takes you through setting up a remote button in xbmc to disable the service.
While I’d be happy with the remote button option, turning the entire service on and off doesn’t sit well with me, what if I want to set mood lighting from my phone ūüôā

So I had a little play with hyperion-remote and thought yeah I can script that. So below is my quick and dirty alternative to disabling the service.

First connect to the PI and create a new file (I called it hyperion_toggle_black.sh)
nano -w hyperion_toggle_black.sh

#!/bin/bash
priority=222

hyper_check=`hyperion-remote -l | grep -i ""priority" : $priority"`
echo $hyper_check
if [ -z "$hyper_check" ]
then
   hyperion-remote -p $priority -c black
else
   hyperion-remote -p $priority -x
fi
exit 0

Next make the script executable
chmod +x hyperion_toggle_black.sh
Now you can test it using ./hyperion_toggle_black.sh
Run it a few times and see that the LED’s go on and off.
The important part of this is the priority, XBMC seems to use a priority of 1000, so any number lower should be great. The android app and hyperion-remote seems to default to 50 or 100. Since I want these to still work I need a value higher than these. 222 should be good, but your free to change it if you like. If for example you want a complete off, setting it to 0 or 1 should be above everything else.
This script basically gives hyperion another job at priority 222 to set all the LED’s black, if there is no current priority 222, and clears priority 222 if it already exists. hence the toggle. On the plus side this wont require root privileges to start/stop the service.
Now you can edit the remote keymap file
nano -w /home/pi/.xbmc/userdata/keymaps/remote.xml
Because I’m using an LG TV and the symlink seems fairly basic, I’m limited to what buttons I can assign. I’ve already previously added a ‘Home’ button to my pause, So I’ve decided to change this slightly by using the pause button to trigger the script while it’s already on the Home screen.
Adding the line  lines
<pause>XBMC.System.Exec(“/home/pi/hyperion_toggle_black.sh”)</pause>
Within the Home>Remote section, but keeping the Global>Remote as
<pause>XBMC.ActivateWindow(Home)</pause>
This took a little playing around to work out which buttons I can use, the blog post at http://forum.osmc.tv/showthread.php?tid=6978 gave me the debug and tail commands to use.

Reboot the PI and viola. There’s about a 1 second delay between pressing the button and the LED’s going off/on, but I can live with that for now.

I didn’t need the XBMC notifications like the other blog post, but if this is something you want, you can mash my script to his and get your own thing ūüôā

Notes:
I’m not entirely sure if turning the LED’s Black actually turns them off (i.e. no power) so I may in the future expand it with maybe a relay to control the actual power to the LED’s, but this would break the priority thing unless I put in some checks and run the script in the background.
I could also then add a push button to the PI’s GPIO triggering the on/off script at more of a physical level, hey I could even add a few buttons to be able to select an effect. but for now I’m happy to do all of that via my phone, and just have a priority function for XBMC.

xlib_shm Zoneminder Got unexpected memory map file size X, expected Y

After upgrading my Ubuntu system first from 12.04 to 13.10 then onto 14.04.1 I started getting problems with xlib_shm
It wouldn’t start up, all the cameras were working fine from the web interface but nothing to the TV.

So I started troubleshooting and found the following error coming back at me.

shared_data_size=12166644
mem_size=884
Got unexpected memory map file size 4056640, expected 884

It doesn’t really make any sense though, it take you down the path of increasing the shmmax an shmall. But this was all working fine before the update and these values haven’t been changed. Still I increased them just incase.
No joy.
I also tried to recompile xlib_shm incase (being upgraded a few times) too much had changed. This unfortunately fell flat on its face when it can’t compile there’s stuff missing (we’ll come back to that).
After going round in circles for quite a while, I found while checking the camera configs that each camera had been set to 8bit greyscale. I set this back to 24bit colour. And restart. hey presto! my tv is once again filled with cameras.
I do still have a problem at the moment though, although the cams are now displaying they are frozen. I’m not sure though if this is more to do with how I start xlib_shm, and think it may be crashing out but not completely enough to take the display away.
So if your having problems with xlib_shm after upgrading either your OS or Zoneminder, check that your cams are setup properly. I almost missed the colour as it’s nighttime and they are grey anyway ūüôā
As for the compile problems. It looks as though upgrading through 2 versions of Ubuntu has removed some packages, I’ll have to try and work out what incase I need to compile again.

Raspberry PI + Maplin WH1080 Weatherstation

Download and write to SD Card the debian image for Raspberry PI.
Boot and connect via SSH (Putty from Windows)
Change to root using sudo su – alternative use sudo in front of commands. Using sudo su – is bad, but I always do it.

Run

apt-get update
apt-get upgrade

Download the latest Debian Weewx version (http://sourceforge.net/projects/weewx/files/) using wget. Then run

dpkg -i weewx_2.6.4-1_all.deb

This will most likely throw error errors about missing dependencies. Install them using apt-get install

dpkg -i weewx_2.6.4.1_all.deb
(Reading database ... 75409 files and directories currently installed.)
Preparing to replace weewx 2.6.4-1 (using weewx_2.6.4.1_all.deb) ...
Unpacking replacement weewx ...
dpkg: dependency problems prevent configuration of weewx:
 weewx depends on python-configobj (>= 4.5); however:
  Package python-configobj is not installed.
 weewx depends on python-cheetah (>= 2.0); however:
  Package python-cheetah is not installed.
 weewx depends on python-imaging (>= 1.1.6); however:
  Package python-imaging is not installed.
 weewx depends on python-usb (>= 0.4); however:
  Package python-usb is not installed.
dpkg: error processing weewx (--install):
 dependency problems - leaving unconfigured
Errors were encountered while processing:
 weewx

So in my case I run
apt-get install python-configobj python-cheetah python-imaging python-usb
During the install you’ll be asked several configuration questions, fill them in (You can always edit the config file later if you make a mistake).

For the maplin WH1080, Select the FineOffsetUSB.

Once installed weewx should attempt to start (if not you can start it with /etc/init.d/weewx start). Check the syslog for any errors
tail /var/log/syslog -n 50
If all goes well you should see something like

wxengine: Initializing weewx version 2.6.4


wxengine: Using Python 2.7.3 (default, Mar 18 2014, 05:13:23) #012[GCC 4.6.3]


wxengine: pid file is /var/run/weewx.pid


wxengine: Using configuration file /etc/weewx/weewx.conf


wxengine: Loading station type FineOffsetUSB (weewx.drivers.fousb)


fousb: driver version is 1.6


fousb: polling mode is PERIODIC


fousb: polling interval is 60


fousb: altitude is 4.2672 meters


fousb: pressure offset is 0.0


fousb: found station on USB bus=001 device=005


wxengine: StdConvert target unit is 0x1


wxengine: Record generation will be attempted in 'hardware'


wxengine: The archive interval in the configuration file (300) does not match the station hardware interval (60).


wxengine: Using archive interval of 60 seconds


archive: Created and initialized table 'archive' in database 'weewx.sdb'


wxengine: Using archive database: archive_sqlite


stats: Created schema for statistical database


stats: stats database up to date.


wxengine: Using stats database: stats_sqlite


wxengine: Starting up weewx version 2.6.4

If you encounter errors you can edit your weewx configuration using nano -w /etc/weex/weewx.conf
Once you’ve finished editing press ctrl+x (to exit), then y(to save), then enter(same filename). Then restart weewx using /etc/init.d/weewx restart
If all has gone well you may also see entries in your syslog like
weewx[12559]: archive: added record 2014-09-20 18:32:44 UTC (1411237964) to database 'weewx.sdb'; table 'archive'
You should also have /var/www/weewx loaded with files.
As we haven’t yet installed a webserver though you can’t view them.
We’ll install Apache2 Server to handle our webpages.
apt-get install apache2
Once apache is installed open the weewx pages by visiting http://{raspberry pi ip address}/weewx from your browser. e.g. http://192.168.1.52/weewx
This setup does leave weewx running as root, not really something you would do for a system running on the internet. but outside the scope of securing your server for here.
Checkout http://www.weewx.com/docs/usersguide.htm#installing for more info on installing, problems and further guides for configuring your weewx installation.
Also checkout http://davies-barnard.co.uk/2013/12/weewx-rasp/ for a better looking skin template (the link is in one of the comments).