Posts Tagged 'Ruby'
2016-03-06

A state of semi-focused relaxation, with a smidgeon of oomph, and a smattering of zing, is just the thing to help me unwind. For some reason, the tones of The Mermen always seem to direct me towards that state. Live recordings of Instrumental Surfadelic music puts my mind in a wonderful place for coding, crafting, sleeping, driving, creating, and simply breathing.

For the most part, music listening to me means "put everything on random". While this is fine for most things, there are times when I want to listen to more than one track at a time by any given artist. Since the majority of my music listening involves using MuttonChop! for playback, and most of that playback happens on Wind (damn, that old machine needs updating), a bit of code with HTTP requests should do what I need.

the Need

What I really wanted was a script that would:

  1. take a number as an argument
  2. get a list of all Mermen tunes from Wind
  3. pick a random index in the tune list
  4. starting at the index, have Wind queue X number in a row from the list

Enter the ruby

#!/usr/bin/env ruby
require 'open-uri'
require 'json'

#how many tracks are going to be played?
num ARGV[0].to_i
num if num == 0

#what is the URL for all mermen tracks on wind?
url "http://wind:2876/audio/search/mermen"

#get the text from the url
text openurl ).read()
#parse the text to JSON data
data JSON.parse(text)
files data['files']

#base zero offset
bzo num-1

#get a random number between 1 and the number of mermen tracks - the num we want to play
queue_index rand(files.count-(num))

#loop from our random number to random number + our base zero offset
(queue_index..queue_index+bzo).each do |i|
  # get the file at this index
  file files[i]
  #get the id of the file at this index
  id file["id"]
  #create a queue url for this file
  new_url "http://wind:2876/queue/add/audio/#{id}"
  puts "#{i}#{new_url}"
  #call the url to add the file to the queue
  opennew_url )
  #don't go to fast
  sleep 0.1
end

For easier copy/paste, the code is available at http://hoof.jezra.net/snip/os

So there you have it, a nice script to do exactly what I want. Now, when I need to get into that certain place, a quick 'wind_queue_mermen.rb 5' does the trick. Why stop there? A bit of blather config editing, and queuing five Mermen tunes is as easy as picking up the Cronos microphone and saying "queue five mermen". BOOYAH!

Now quite reading, and go to https://archive.org/details/Mermen for some wonderful live recordings.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 2
Answer:
required
2015-04-08

Not too long ago, I purchased a programmable remote for my DSLR camera so that I could take long exposures as well as multiple exposures in order to create time lapse videos. While the timer does a great job, I still needed a way to convert a series of images into a video.

Fortunately, ffmpeg does a great job of converting a pile of images into a video file, and I most certainly have ffmpeg installed on my laptop. Now all I need is a bit of code to:

  1. search for some files
  2. copy and rename the files
  3. always skip step 3 :)
  4. use ffmpeg to convert the files to a video

When the script runs, it looks for a directory named 'images' and recursively looks for files in that directory (my camera tends to make multiple directories of files). Then the files are copied into a temp folder and given a 6 digit name: 000001, 000002, 000003, etc. Finally, ffmpeg converts those 6 digit named files into a video.

Enter The Ruby

#!/usr/bin/env ruby
require 'fileutils'
require 'date'

#where will we look for files?
LOCAL_DIR File.expand_pathFile.dirname(__FILE__) )
TEMP_DIR File.join(LOCAL_DIR"temp")
IMAGES_DIR File.join(LOCAL_DIR"images")
#if the temp dir exists, delete it and its contents
if Dir.exists? TEMP_DIR
    FileUtils.rm_rf TEMP_DIR
end

#make the temp dir
FileUtils.mkdir TEMP_DIR

#keep track of how many files we have
@file_count 0

def copy_files(location)
  files = []
  dirs = []
  Dir.foreach(locationdo |name|
    #ignore . and ..
    if name!='.' and name!='..'
      #what is the path of the item?
      path File.join(location,name)
      #recurse if path is a directory
      if Dir.exists? path
        dirs << path
      else
        files << path
      end
    end
  end
  #sort the directories by name and recurse
  dirs dirs.sort()
  dirs.each do |d|
    puts "DIRECTORY: #{d}"
    copy_files(d)
  end
  #sort the files by name and recurse
  files files.sort()
  files.each do |d|
    new_name @file_count.to_s
    #increment the file_count
    @file_count+=1
    ## pad the new file name with 0
    (6-new_name.length()).times do
      new_name "0"+new_name
    end
    #determine where the new file is going
    new_file File.join(TEMP_DIRnew_name)
    #what is the command to copy the file?
    copy_command "cp #{d} #{new_file}"
    puts copy_command
    #run the command
    `#{copy_command}`
  end
end

#recursively get a list of all files in the current directory
copy_files(IMAGES_DIR)

#what day is it?
ymd Date.today.strftime("%Y%m%d")

#what video command should we run?
cmd "ffmpeg -y -f image2 -i temp/%06d -b:v 50000k  #{ymd}_timelapse.avi"
#run the command
`#{cmd}`

By default, the ffmpeg command will create a video at 10 frames/second.

My first time lapse was during Sunset on the Equinox

Sadly, I didn't adjust the camera properly when capturing the April 4th Lunar Eclipse. However, considering I set everything up 7 hours before the eclipse, I think I did a fairly sweet job of determining where to point the camera. :)

Now quit reading, slow down, and speed up.

Comments
2015-04-22 Alison Chaiken:
'gst-launch multifilesrc location="somematchingstring"' would work as well.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
8 minus 2
Answer:
required
2014-12-13

Sometimes, it is really nice to know what the weather is like without having to look out a window. Similarly, it is nice to know what the weather will be like in the future. Fortunately, getting weather information isn't that difficult.

For my needs, I wanted to get the weather data for a few cities in the Golden State, and have the data spoken to me by a computer.

For accessing the weather data, I headed over to http://api.wunderground.com/weather/api/ and signed up for a free API account. The free account limits me to 500 API calls per day and 10 calls per minutes. Since I am only interested in getting weather data a few times a week, the limits for the free account are of no real concern.

After getting my API key, it was time to fire up geany and smash out some Ruby code to do what I need to do, which is:

  • get the data from the API
  • save the data in a file
  • transform the data into a more speakable string
  • speak the string!

Enter the Ruby

#!/usr/bin/env ruby
require 'open-uri'
require 'json'
require 'fileutils'

key='########'
city=ARGV[0]
forecast_num ARGV[1].to_i || 0

#was city defined?
unless city
  puts 'you must enter a city name (or zip code)'
  exit()
end

#define some file locations
this_dir File.dirname(__FILE__)
data_file File.join(this_dir"#{city}.json")
temp_file data_file+".tmp"
#if the data file doesn't exist or it is more than an hour old
if not File.exists?(data_fileor File.mtime(data_file) < Time.now() - (1/24.0)
  #we need to dowload fresh data
  url="http://api.wunderground.com/api/#{key}/forecast/q/CA/#{city}.json"
  uri URI.parse(url)
  begin
    #read the text
    txt uri.read
    #write to the temp file
    open(temp_file"w"do |f|
      f.puts txt
    end
    #mv the temp file, clobber if necessary
    FileUtils.mv(temp_filedata_file:force => true)
  rescue
    puts "Failed to get weather data"
  end
    
end
#process the text from the data_file
txt IO.read(data_file)
weather JSON.parse(txt)
forecasts weather['forecast']['txt_forecast']['forecastday']
title forecasts[forecast_num]['title']
forecast forecasts[forecast_num]['fcttext']
#do some regex processing of the forecast
forecast forecast.gsub(/mph/"miles per hour")
forecast forecast.gsub(/([0-9]+)F/'\1 Fahrenheit')
#remove any %20 from  the city
city city.gsub(/%20/,' ')
string = (forecast_num.zero?) ? "" "Forecast "
string += "for #{city}#{title}#{forecast}"

#speak the string
`speak_string "#{string}"`

#comply with Section 2(d) @ http://www.wunderground.com/weather/api/d/terms.html
puts "This data is from Wunderground.com"

Just to make it a bit easier for me to get the weather or the forecast, this script is running on Cronos (my Minnowboard Max voice input machine) and is triggered via Blather when I say "what is the weather" or "what is the forecast". SWEET SAUCE!

Oh, the script references a command named "speak_string" which is a very simple wrapper for Festival and it looks like

#!/bin/sh
echo "$1" | festival --tts

Now quite reading, and go talk to a computer.... or look out the window and see some weather.

Comments
2014-12-31 Alison Chaiken:
Hmm, on Debian Testing my installation of festival doesn't know about any voices. I installed some voices (odd thing to say!) but it still complains it can't find its default voice. There doesn't appear to be a relevant setting in /etc, and the man page offers no clues. Have you configured Festival somehow? I used to run it, but apparently not recently.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 2
Answer:
required
2014-12-06

Getreel

The Need

On my satellite internet plan, I have a rather limited amount of data that I can utilize each month before I am throttled to about 100Kbps, and it would be rad if I could use my data for both work and edutainment.

The silver lining lies in the so called "bonus" data that is available from 2am-8am. For a while, I have been using atd to schedule software updates on my various Linux boxen to take place during this bonus time, and what I really needed was a way to download some video tutorials for watching later.

The Tool

Fortunately for me, there is a very nice utility written in python called youtube-dl that will not only download videos from youtube, but from a large number of sites hosting videos including PBS (yea, I love nature documentaries and Patrick Stewart in MacBeth).

The Wrapper

Because I didn't want to SSH into my Muttonchop! HTPC and use atd and youtube-dl for scheduling, I decided to write a wrapper for youtube-dl that needed to have two features:

  1. a web API for adding, listing, deleting URLs in a list of URLs
  2. a setting to determine when to download videos from the list of URLs

After a bit of Ruby hacking, Getreel was born and it is available at https://gitorious.org/getreel.

The project also includes an API testing app called 'api.rb' that will allow for adding, listing, and removing URLs from a Getreel running device by another device on the same network. Basically, it is an easy way for me to send commands from the terminal on my laptop to the Getreel instance on my HTPC. yea, I'm lazy like that. :)

Special thanks go out to windigo for the sweet HTML, Javascript, and CSS UI for web input.

Now quit reading, and go solve a problem.

Comments
2014-12-06 kathy:
If the people understand this language, and what you are saying, I think it is awesome! Keep up the good work!
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
4 minus 4
Answer:
required
2014-11-01

Updating My NaNo Novel

Today I woke up eager to get cracking on my Novel for National Novel Writing Month. Although the site didn't crash this year, booyah!, there was a rather persnickety issue with deadlocks in the database that was keeping participants from updating their novel word count. Brutal

After a few hours of debugging and some quick/dirty hacks, and then a more robust fix, the servers were chugging along at a fairly good clip and I could finally get down to typing some words.

Hold on a sec! There is no way that I'm going to do a bunch of typing and then copy/paste my words into the NaNoWriMo word count checker every time I do a bunch of writing. To be less dramatic, this morning I was trying to take advantage of the new wordcount Write API when I became aware of the database problems.

Anyway, surprise surprise I am using Geany to write my novel, and geany has a nice feature where one can define a project and set build commands for that project. Now I know what you are thinking, a static text file doesn't need a build command, and while this is true, I have decided to use the build command to run a script that will count the words in my novel and upload the count to the NaNoWriMo server.

My workflow goes like this:

  1. Type type type my novel
  2. press F8 to run the 'build script'
  3. always skip step 3
  4. rejoice at the simplicity

The script is a quick bit of Ruby with some wrappers around some shell utilities.

Enter The Ruby

#!/usr/bin/env ruby
require 'digest/sha1'
require 'net/http'

#--- config ---#
name 'MY_NANO_NAME'
secret_key 'MY_SECRET_KEY'
novel_file 'MY_NOVEL.txt'
count_file 'count.txt'

#get the word count from the novel
wc `wc -w #{novel_file}`.to_i

#get the last recorded count
lrc `cat #{count_file}`.to_i

#is the word count greater than the last recorded count?
if wc lrc
  #update the lrc
  `echo #{wc#{count_file}`
  #create the hashable string
  hs "#{secret_key}#{name}#{wc}"
  puts hs
  #hash the string
  hashed Digest::SHA1.hexdigesths )
  puts hashed
  #prepare the data
  data = {namenamehashhashedwordcountwc}
  #create and send the request
  http Net::HTTP.new('nanowrimo.org')
  request Net::HTTP::Put.new('/api/wordcount')
  request.set_form_datadata )
  response http.request(request)
  puts response.body

end

For easier copy/paste, the code is available at http://hoof.jezra.net/snip/oq.

Now quit reading, and go write, or read.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 minus 1
Answer:
required
2014-08-31

Cronos!

Not too long ago, I received a Minnowboard Max, the dualcore 1.3Ghz model, and it is a sweet bit of kit.

Hear is my quick review of the hardware.

Good

  1. A power button!
    Every time I shutdown an SBC like the Raspberry Pi or Beaglebone by pulling the power connector, I cringe and hope I don't mess up the file system. Thank you Minnowboard Max for the graceful shutdown.

  2. intel graphics
    A graphics chip with Open Source drivers maintained by the chip manufacturer? yes please!

  3. x86 processor
    Apparently this makes #2 possible. Well not really, but there don't seem to be any other SBCs that have an Open Source driver for the graphics chip that are not x86 based.

  4. SATA connector
    at some point, it would be great to add copious amounts of storage to this SBC

Bad

  1. No OS images are available with a kernel that has GPIO enabled. To be fair, this is not the boards fault. On a side note, I tried to compile the 3.16 kernel with GPIO and PWM support but I ended up borking something. No surprise there. /me is horrible at compiling kernels.

  2. UEFI in the firmware. bummer. This isn't as bad as it sounds either, although it is slightly more complicated than "install boot media and power up the device". Personally, I would love to skip UEFI and let the underlying BIOS handle all of my startup needs.

wow. how phenomenally indepth. :)

So what did I do with it? I'm glad you asked....

What I did with it

First, I tried to install Arch, but I couldn't get the /etc/fstab to properly recognize the /boot partition that had the UEFI boot jibberjabber on it. Can I blame this issue on UEFI? Does it have something to do with the UEFI requirement that the boot partition is formatted FAT32? Does that mean I can blame Microsoft as well? Does it matter that I was installing to the Micro SD card?

we may never know. Perhaps I was too hasty to give up, but I was itching to create, and not to debug.

Following the instructions at elinux.org, I installed Debian 8 (jessie) using the AMD64 netinstall image and then began installing the packages that I needed for compiling software and running scripts. A USB soundcard and an 802.11n wireless card were collected for the build. Then I put shit together.

Make A Case

For the most part, I recycled the case from the horrid Odroidx computer.

Suspend the board

zip ties, yarn, and small eye screws make great stand-offs.

Here is the USB card with small speakers connected to it. A sweet microphone was later added to the mix.

Bend some metal

METAL!!!!!
This is part of the sheet metal that was donated to me by the recipient of windchime #3. Thanks Buddy!

What you are looking at is a microphone holding bracket screwed to the wall.

Microphone check! one two one two

Oh my, is that a sweet rewired mic that I see there? yes, yes it is.

Did I mention that I reused a lot of projects in this project?

On the wall!

There she is: Cronos, right next to the "music room".

Recycling projects made this a bit of a breeze. The toughest part was wiring the electrical outlet behind the case so that I wouldn't have to expose any wires. keeping it clean. BOOYAH!

Software

A build is fine and dandy, but what is it running, and what does it do?

Chime the hour

Every hour, on the hour, cron runs a fairly simple Ruby script to play a "chime" for each hour of the hour. The cron code looks like

0 * * * * /usr/bin/ruby /opt/chime/chimer.rb

And the ruby script is:

#!/usr/bin/env ruby

#where is the chime?
this_dir File.expand_path(File.dirname(__FILE__))
chime File.join(this_dir'chime.ogg')
vol 1.0

hour Time.now.hour
if hour 22 or hour 7
  vol 0.1
elsif hour 21 or hour 8
  vol 0.4
end

#what command plays the audio?
play_cmd "gst-launch-1.0 playbin uri=file://#{chimevolume=#{vol&"
puts play_cmd

hour -=12 if hour 12
hour.times do |i|
  system(play_cmd)
  sleep 2
end

And the chime audio is a recording of low note from wind chime number 2 with the pitch shifted. Hey! Where is the video of windchime 2?

Text to speech

Thanks to the USB sound card, the computer has audio out other than through HDMI.

For converting Text to Speech, I am using festival with Arctic voices.

Aside from the system itself using festival for text to speech, I am also running a modified version of my ruby webrick based web server for text to speech that uses festival and allows me to send text to speech commands to the computer from any network connected device.

I should mention that although the Minnowboard Max is a fairly speedy little computer, festival text to speech is quite the resource consumer and there is a delay between when a request for speech happens and when the resulting audio is actually heard. This lead to a feature in Blather.

Damn it computer! Do what I tell you!

Blather handles all of the voice commands for telling devices on my network what to do.

Because I needed some sort of instant feedback for whether Blather accepted my voice command or not, an option was added for running a script when a valid voice command was detected. Similarly, an option was added for running a script when an invalid voice command was detected.

In this implementation, the "valid script" plays an "affirmative beep" and the 'invalid script" plays a "negative beep". Since no one offered to help me with the audio, I busted out my cheapo USB mic and recorded my own files. :) oh man....

Affirmative: bing!

Negative: booduhbooduh

Well there she is. Now if you will excuse me.... "Play Iron Maiden"

Comments
2014-09-05 Alison Chaiken:
Another amazing and inspiring post! I'd be happy to help you compile the kernel if you want to take another whack at it.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
7 minus 2
Answer:
required
2014-05-12

Due to a limitation of my new horribly wretched Internet Service Provider, I no longer have a static IP address, nor do I have an accessible dynamic IP, and thus my ability to easily access my home network while I am "oot and aboot" is non-existent. If only there were someway I could hack together a software solution to solve my problem...

Oh, surprise surprise, that is exactly what I did. ;) Yea, I put emoticons in blog posts.

The plan went something like this:

  1. setup an HTTP basic_auth server that will let me add a command to a list by using the POST method
  2. run a script on a machine inside my home network that will:

    1. pull the list of commands from the server
    2. check if the commands match a script in a specific directory
    3. run the commands and make a Jezra happy

The most difficult part of this plan seemed to be the creating of the basic_auth server, however, in Ruby there is a really nice standard library module called Webrick that makes it very easy to make a webserver without the need of a third party library, and webrick also has basic_auth support. sweet sauce!

After a quick bit of hacking, I had a working solution.

Enter the Ruby

#!/usr/bin/env ruby
require 'webrick'
require 'json'
require 'yaml'

#commands will be stored in a list
@@commands = []

## build some basic authentication ##
#
#start creating the config
config = { :Realm => 'myCmdProxy' }
#create an Htpasswd database
htpasswd WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
#add two users to the password config
htpasswd.set_passwd config[:Realm], 'writing_user''writing_password'
htpasswd.set_passwd config[:Realm], 'reading_user''reading_password'
#flush the database (write it to file)
htpasswd.flush
#add the database to the config
config[:UserDB] = htpasswd

#create a global BasicAuth based on the config
@@basic_auth WEBrick::HTTPAuth::BasicAuth.new config

#a class that contains an authenticate method
class AuthChecker WEBrick::HTTPServlet::AbstractServlet
  def authenticate(requestresponse)
    @@basic_auth.authenticate(requestresponse)
  end
end

#create a list class to handle calls to /list
class List AuthChecker
  def do_GET (requestresponse)
    authenticate(request,response)
    #return a list of the alarms pending for execution
    response['Content-Type'] = 'text/json'
    response.body JSON.dump(@@commands)
    #clear the list
    @@commands = []
  end
end

class Add AuthChecker
  def do_POST (requestresponse)
    response['Content-Type'] = 'text/plain'
    @@commands << request.query['command']
    response.body request.query['command']
  end
end

# Initialize our WEBrick server
if $0 == __FILE__
  #create a webrick server
  server WEBrick::HTTPServer.new({:Port=>8888})
  #what urls do we need to mount?
  server.mount('/add'Add)
  server.mount('/list'List)
  #check for user interrupts
  trap "INT" do
    server.shutdown
  end

  #start the server
  server.start
end

For easier copy/paste, the code as been put in the hoof at http://hoof.jezra.net/snip/om. So that I don't accidentally run commands multiple times, the list of commands is truncated whenever the list is requested.

So how do I add commands to the list? By simply opening the terminal on a user-centric device, and running a properly crafted curl command such as
curl -u writing_user:writing_password http://CMD_SERVER_NAME:8888/add -d "command=lights_fade_on.sh"
Oh, that seems like a lot of typing, I better make that a shell script named "proxy_lights_on.sh". Now I just run the script when I'm heading home, and the lights will be on when I get there. sweet sauce!

So the server is up and running, commands are being sent to it, but I still need to read the list of commands, check if the commands exist in a specific directory, and then run the commands. Wooooweeeee!

How about some more Ruby?

#!/usr/bin/env ruby

require 'net/http'
require 'json'

host "CMD_SERVER_NAME"
username "reading_user"
password "reading_password"
command_dir "./commands"
port 8888

http Net::HTTP.new(hostport)
http.start do |http|
  req Net::HTTP::Get.new("/list")
  req.basic_auth(usernamepassword)
  response http.request(req)
  resp response.body
  commands JSON::parse(resp)
  commands.each do |cmd|
    puts cmd
    cmd_path File.join(command_dir,cmd)
    if File.exists? cmd_path
      print`#{cmd_path}`
    else
      puts "Command #{cmd_pathdoes not exist"
    end
  end
end

Yup, this is in the hoof as well: http://hoof.jezra.net/snip/on. This reader script is run every 5 minutes via a cron job. If I needed a more immediate sending of commands, I could increase the cron frequency to run every minute; or better yet, I could have the server emit the commands as a Server Sent Event whenever a command is added. For my needs however, five minutes is fine.

Now quit reading, and go curl -u writing_user:writing_password http://CMD_SERVER_NAME:8888/add -d "command=queue_hella_iron_maiden.sh"

Comments
2014-06-03 Alison Chaiken:
So:
1. You're adding commands by POST
2. You're sending your password by plaintext?
3. And you're allowing remote execution?
2014-06-03 jezra:
in this instance:
1. yes, although I might as well have used GET
2. yes, that is how http basic_auth works, and I didn't feel like getting a cert for this example
3. sort of.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 6
Answer:
required
2014-04-15

Let me tell you about my internet service provider. Because of my location, my only options for "hightspeed" internet is HughesNet, a satellite based internet service provider. The price is high, the service is lackluster, the speed is low, and the monthly data cap is wretched. I have a 5 Gigabyte monthly allowance that can be used from 8AM til 2AM and another 5 Gigabyte allowance that can be used from 2AM - 8AM. Yea, most mobile phone data plans are just as good.

Oh yea, and that data allowance includes both download data and upload data.

As soon as my monthly allowance is consumed, my network is throttled down to dialup speed, which makes using the contemporary internet quite problematic.

Since I didn't want to have to endure that crap, I needed a way to decently keep track of my data usage. Unfortunately, all of the resources provided by HughesNet for tracking data usage are lousy and don't meet my needs as a user of their service. Fortunately HughesNet's modem provides some semi-accurate data that I can parse and display in a way of my choosing.

So with my trusty text editor in hand, I used a bit of Ruby to scrap data from the modem and make a rough projection to determine if the data usage rate so far this month will result in running out of data.

NOTE: this script was updated on 2014-05-07 and is available at http://hoof.jezra.net/snip/ol Enter the Ruby

#!/usr/bin/env ruby

#------------------------------------
# What is Your Data Cap in Gigabytes?
#-------------------------------------
data_cap_in_gigabytes 5

#we will need to open a url
require 'open-uri'

#convert a gigabyte number to bytes
def gigabyte_to_bytes(i)
  1000000000 #that is how they determine a Gig
end

#what is the total allowance in bytes?
ALLOWANCE gigabyte_to_bytes(data_cap_in_gigabytes)

#what is the URL we need to scrape?
URL "http://192.168.0.1/cgi-bin/index.cgi?Command=11"

#read the text from the URL
text open(URL).read()

####use some sloppy regex to get important data
#get the gigs remaining
match_data text.match(/(?<remaining>[0-9\.]*) GB<br>Remain </)
remaining_bytes gigabyte_to_bytesmatch_data['remaining'].to_f )

#get the sleepytime gigs remaining
match_data text.match(/(?<remaining>[0-9\.]*) GB<br>Remain</)
remaining_bonus_bytes gigabyte_to_bytesmatch_data['remaining'].to_f )

#find the date of the next reset
match_data text.match(/Resets in (?<days>[0-9]*) days, (?<hours>[0-9]*) hr and (?<min>[0-9]*) min/)
offset match_data['days'].to_i*86400 match_data['hours'].to_i*3600 match_data['min'].to_i*60
NOW Time.now()
next_reset_date NOW offset

#when was the reset date? A month before the next reset date
next_reset_date.year
next_reset_date.month
#what month was previous?
-=1
12 if == 0
next_reset_date.day
next_reset_date.hour
next_reset_date.min
reset_date Time.localymdhM)

#how much time has elapsed?
elapsed_time NOW reset_date

#how fast is the data being used?
used_data ALLOWANCE remaining_bytes
data_per_second used_data elapsed_time

#how many seconds will it take to use the remaining data?
seconds_to_use_remaining_data remaining_bytes data_per_second

#when will the shitty throttling occur?
throttle_date NOW+seconds_to_use_remaining_data
puts "---- Projected Throttling ----"
puts "Next Reset Date: #{next_reset_date}"
puts "Throttling Date: #{throttle_date}"

#what is the difference in seconds?
difference_in_seconds next_reset_date throttle_date
#is the throttle date before the next reset?
throttled next_reset_date throttle_date
seconds difference_in_seconds.abs
#convert the difference is seconds to days, hours, minutes, seconds
days = (seconds 86400).to_i
seconds -= days 86400
hours  = (seconds 3600).to_i
seconds -= hours 3600
minutes = (seconds 60).to_i
seconds -= minutes 60
#print the difference in d h m format
if throttled
  puts "Projected throttling in %d days, %d hours, %d Minutes" % [dayshoursminutes]
else
  puts "Throttling is not project at this time"
end
puts ""

For easier copy/paste the script is also available at http://hoof.jezra.net/snip/oj.

This script was written to use the less-than-decent html output of the t1100 HughesNet modem and may or may not work on other modems. Your mileage may vary.

Now quit reading, and go solve a problem that needs solving.

NOTE: this script was updated on 2014-05-07 and is available at http://hoof.jezra.net/snip/ol

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
3 minus 3
Answer:
required
2014-01-05

One of my tasks at work was to write a script to process a large number of codes. However, when I needed to write the processing script, the codes did not yet exists. What I did know, was that the codes would be text strings, and to get my job done I really just needed a bunch of made up test data that consisted of text strings.

After a few minutes in my favorite text editor, I had a quick and dirty Ruby script that would generate a number of codes for me, and this would allow me to get cracking on the script that would process the codes.

Enter the Ruby

#!/usr/bin/env ruby

#how many codes do we need to generate?
num_codes ARGV[0].to_i
num_codes 100 if num_codes.zero?

#what characters are valid for a code?
@chars %w{a b c d e f g 1 2 3 4 5 6 7 8 9 0 A B C D E F G H I J K L M N P Q}

#function to create a new code
def make_code
  #start with a empty string
  code ''
  #loop 10 times
  (1..10).each do
    #add a random valid character to to the 'code' string
    code << @chars.sample
  end
  return code
end

#start with an empty list of codes
@codes = []

#loop to the number of codes we need to generate
(1..num_codes).each do |i|
  #make a code
  code make_code
  #does this code already exist? if so, it is not unique
  while @codes.include?(codedo
    #make another code
    code make_code
  end
  #add the code to the codes list
  @codes << code
  #print the code
  puts code
end

For easier copy/paste, this script is also available at: http://hoof.jezra.net/snip/oi

OK, Time Out: go look at the list of valid code characters. There are a few things that I don't like doing when generating codes....

  • mixing case: if at any time, someone needs to read the code to someone else, DO NOT MIX CASE.
  • using both lower case 'L' and number 1 as valid characters: they are far too easy to confuse
  • using capital "O" and number 0 as valid characters: again, they are easy to confuse.

Alright, back to the code....
When I refer to the script as quick and dirty, I mean that the script is written quickly and not written for efficiency. There are certainly some ways to make this script faster, but for what I needed, the script works just fine.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
5 minus 1
Answer:
required
2013-12-28

The other day, while downloading the latest episode of Linux Outlaws, I wondered why I couldn't queue the episode to play first thing in the morning using MuttonChop, then I thought "Do I really hate myself enough to wake up to these guys blabbing away?" Apparently, the answer is "yes". ha!

(play the the A-Team Theme)
The plan went like this:

  1. write a script to:
    1. get the list of cast subscriptions from a MuttonChop computer
    2. find the first cast with a title that matches a given string
    3. always skip step 3
    4. find the most recent downloaded and unplayed episode of that cast
    5. queue the unplayed episode
  2. make an alarm for the Ruby Web Alarm Project that calls the script from step 1 with "Outlaws" and the search parameter
  3. always skip step 3
  4. set an alarm for the morning (fade_random_vivaldi.sh should do the trick)
  5. set the alarm from step 2 to run at the same time as fade_random_vivaldi.sh

... and that is exactly what I did.

OK, not exactly. The script defaults to search for Outlaws. ;)

For this script, I decided to use the Ruby programming language and this is the first Ruby script I've written that uses the Optparse library from the ruby standard library. The Optparse library makes handing the parsing of command line arguments quite easy. Having used an option parser in Vala and Python programming languages, I was interested to see how option parsing was handled in Ruby.

OK, that's enough jibber-jabber....

Enter the Ruby

#!/usr/bin/env ruby
require 'open-uri'
require 'json'
require 'optparse'

#define some default values for our options
options = {:host=>"localhost":cast_search_string=>"Outlaws"}

#build the options
OptionParser.new do |opts|
  opts.banner "Usage: #{File.basename(__FILE__)} [options]"
  opts.on("-s""--server server_name"String"The MuttonChop server to query"do |s|
    options[:host] = s
  end
  opts.on("-c""--cast search_string"String"The cast to search for"do |c|
    options[:cast_search_string] = c
  end
end.parse!

#show the options
puts "Host: #{options[:host]}"
puts "Search String: #{options[:cast_search_string]}"
#where do we get the list of subscribed feeds from the muttonchop server?
url "http://#{options[:host]}:2876/catcher/feeds"
puts "Feeds URL: #{url}"
#get the returned text from the url
text open(url).read()
#parse the text as json
feeds JSON::parsetext )

feed_id nil
#loop through the feeds
feeds.each do |feed|
  #does this feed's title contain our cast_search_string?
  if feed['title'].match(options[:cast_search_string])
    feed_id feed['id']
    break
  end
end

#if no feed was found, exit
if feed_id.nil?
  puts "No matching cast title"
  exit
end

#where do we find a list of the matching feed's episodes?
url "http://#{options[:host]}:2876/catcher/episodes/#{feed_id}"
puts "Matching Feed's Episodes URL: #{url}"
#read the url's returned text and parse it
text openurl ).read()
episodes JSON::parsetext )

episode_id nil
#loop through each episode
episodes.each do |e|
  #has this episode been downloaded? does a file exist? has it not been played?
  if e["downloaded"and e["exists"and not e["played"]
    episode_id e['id']
    break
  end
end

unless episode_id.nil?
  #what url is needed to queue the unplayed episode?
  new_url "http://#{options[:host]}:2876/queue/add/cast/#{episode_id}"
  puts "Episode Queue URL: #{new_url}"
  opennew_url ).read
else
  puts "No unplayed episodes"
end

For easier copy/paste, this code is also available at http://hoof.jezra.net/snip/oh.

While the script certainly gets the job done, there should really be some error handling for when the host doesn't exist and it isn't possible to open and read a URL, but hey, it certainly suits my needs very well.

Now quit reading, and go listen to something.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
0 plus 1
Answer:
required
2013-10-30

Have I ever mentioned how much I like my n900? If not, let me tell you that I love this device... mostly. GNU Linux Operating System, the ability to write apps in the programming language of my choice; what is there to not like about this four year old marvel? Setting the time.

On the N900, setting the time can be done manually, or automatically when the device is used as a mobile phone with a SIM card. Since I don't use the device as a mobile phone (to me it is the ultimate pocket computer), I need to update the time manually, and the time seems to drift by a few minutes after a couple of months of uptime.

Recently, the USB port on my N900 broke, and I've been using a battery charger to keep a collection of batteries charged for the device. When a battery gets drained, a simple swap of the battery has me up and running in no time. Unfortunately this means that I need to set to manually set the date every time I swap a battery. Oooof, I'm too lazy for that crap, time for a bit of code!

The plan was fairly simple, write a script to:

  1. fetch time data from somewhere
  2. use the date command to set the date

Unfortunately, I couldn't find a really easy way to get the current time from somewhere on the internet. How is that possible?
Fortunately, a bit of code written in PHP and hosted on one of my websites will display the current UNIX Time

The PHP code looks like this :

<?php
//[MMDDhhmm[[CC]YY][.ss]]
echo date('mdHiY');
?>

Alright, the date data is available online, now on to the script to read the data.

Since this script is for an N900, I have a choice of programming languages, and for this script I chose Ruby. After a quick look at the language documentation, the following was hammered out on the N900.

#!/usr/bin/env ruby
require 'net/http'

#create a URL for our url //haha
uri URI('http://www.jezra.net/date.php')

#get the response from the server
res Net::HTTP.get_response(uri)

#get the date from the response, and remove non-numeric characters
date_string res.body.gsub(/[^\d]/,'')

#set the date command
cmd "date -u #{date_string}"

#run the command
`#{cmd}`

Nice and simple.... ;)

Now quit reading, and go solve a problem.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
6 minus 4
Answer:
required
2013-08-04

A few weeks ago, while being entranced by a software bug, I failed to keep an eye on the clock and thus I missed a work meeting. Do you know when that tragedy is going to happen again? never. Obviously, I need to set some sort of alarm...

In my minds eye, I had a vision of what I wanted the alarm to do: play an audio file and flash some lights. Fortunately, the software I've been writing could almost do what I wanted to do. almost. A bit of code hacking needed to be done.

For audio playback, MuttonChop media player was installed on the toaster(which has an amplifier and speaker). A Star Trek Alert was selected as the audio to play when the alarm goes off. However, before the alarm would work as I wanted, I needed to add a bit of code to MuttonChop to make a playing file loop over and over. In the MuttonChop API, this resulted in the following new API calls:

  1. HOST:PORT/player/loop
    retrieves the loop state of the player
  2. HOST:PORT/player/loop/true
    set the player loop state to 'true'
  3. HOST:PORT/player/loop/false
    set the player loop state to 'false' (to be fair, anything that is not 'true' will result in 'false')

Rad, that takes care of the audio aspect of the alarm. On to the lights!

GLMR(the GLRM Light Manipulation Regulator) installed on a Raspberry Pi affectionately named "shitbird", will handle the light needs. However, after running some tests I quickly realized that I would need a quick and easy way to revert the lights to their "pre-alarm" state. A bit of code caking later and two new API paths were added to GLMR:

  1. HOST:PORT/color/previous
    set the LEDs to their previous set color
  2. HOST:PORT/mode/previous
    set the LEDs to their previous set mode

Booyah! Now it is simply a matter of creating an alarm for the Ruby Web Alarm project.

enter the shell script

#!/bin/sh
# call the API to play audio file with ID of 1
curl fruity:2876/audio/play/1
# set the looping to 'true'
curl fruity:2876/player/loop/true
#crank up the volume
curl fruity:2876/player/volume/100

#set the glmr mode to 'twinkle'
curl shitbird:4567/mode/twinkle
#set the LEDs to full red
curl shitbird:4567/color/ff0000

...And finally, there needs to be a way to turn off the alarm. To keep shit sweet, I decided to use the 'Cancel' button on the face of the toaster to run a 'cancel alarm' ruby script.

Enter the Ruby

#!/usr/bin/env ruby
require 'json'
require 'open-uri'

#get the status of muttonchop on fruity
url "http://fruity:2876/player/status"
openurl do |f|
  status JSON.parsef.read() )
  #is the machine playing an alert?
  if status['state']=="PLAYING" && status['album']=='Alert'
    #stop the muttonchop player
    open("http://fruity:2876/player/stop")
  end
end

#check the glmr status on shitbird
open("http://shitbird:4567/status"do |f|
  status JSON.parse(f.read() )
  # if the alarm is running
  if status['color']=='ff0000' && status['mode']=='twinkle'
    #revert the color
    open("http://shitbird:4567/color/previous")
    #revert the mode
    open("http://shitbird:4567/mode/previous")
  end
end

Alrighty! The alarm goes on, and the alarm goes off; but what does it look/sound like?

It may not be the greatest alarm ever, but it is highly effective. Now quite reading, and go set an alarm.

Comments
2013-08-05 Alison Chaiken:
Okay, I admit it's cooler than the Pebble watch.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
7 minus 5
Answer:
required
2013-05-22

Hey, wouldn't it be awesome if I could connect a string of multicolor LEDs to a little computer and control the LEDs over my home network? Yes, as a matter of fact, that would be an excellent thing to do.

Step 1: Aquire LEDs

For this experiment, I purchase two strips of 50 ws2801 Pixel LEDs from a merchant in China and after a few weeks, the LEDs arrived and it was time to get hacking.

Step 2: Hack Some Code

The software for controlling the LEDs needed to have the following features:

  1. a Web UI so that I could control the LEDs with any device connected to my home network.
  2. a simple API that would allow controlling the LEDs with a GET request from any programming language

To meet my needs, I opted to use the Ruby programming language for the server and jQueryUI for the Web Interface. This decision was based on the Ruby language's standard library webrick module which makes it very easy to create a webserver that serves static content, and also makes it trivial to map URLs to specific functions and methods.

The end result is GLMR, the Glrm Light Manipulation Regulator. Wow, what a crappy recursive backronym. Honestly, I constantly forget what the 'R' stands for and I have to look it up.

The Web UI

There isn't too much to the built in Web UI. Enter a hexadecimal, or adjust the RGB sliders and then click Set to change all of the LEDs to the selected color, or click Fade to slowly fade the LEDs to the selected color.

There are also 3 modes for the LEDs:

  1. Chase: one LED is illuminated at a time across the length of the LED string
  2. Full: all LEDs are illuminated
  3. Twinkle: the LEDs randomly turn on and off

Using the API

Since the browser on my n900 is getting a bit old, I decided to write a Python Qt application that uses the GLMR API and it works wonderfully! Thanks Qt!

Wait a second! If the API is so simple, why don't I have more ways to control the LEDs? Aha! I'm one step ahead of you.

Remember Blather the speech recognizer that runs commands? Well, I've configured Blather to control the LEDs and here is the video of Blather and GLMR working together.

Booyah!

Now quit reading, and go hack some code ... and some LEDs

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
4 minus 0
Answer:
required
2013-03-17

Those old coin-op photobooths are awesome. Sit down, close the drape, put some money in the coin slot, and in a few minutes you will have some memories in your pocket. Why can't I have one of those? Oh yea, because they take up a lot of room, use stinky chemicals, and I have no idea how to build one. Fortunately, I have some computer skills.

The Mission

if

Yea, that's a big if. If I had it my way, I'd make an art installation piece that was a digital photobooth with a built in webserver, so that after pictures were taken, one would connect to the photobooth over the internet and download pictures. Unfortunately, I don't have the room or the tools to make a photobooth, but I can get cracking on a much smaller, yet curiously similar project that focuses on the computer and software aspect of a digital photobooth.

The Computer

The computer for this project is a Rev 2 Raspberry Pi. See that little wood block that the computer is screwed to? Good. The Rev 1 Raspberry Pi didn't have any mounting holes and it made putting the device into a custom enclosure a real pain in the ass. The Rev 2 only has two mounting holes and a small block of wood was used for both holes.

I'm still waiting for a apology for the lack of mounting holes fiasco. Oh yea, this computer's name is 'shitbird' because I couldn't come up with another name

The Case

A few years back, my buddy gave me two almost fancy Wild Turkey boxes. One box was used for my media machine, the other was used for this project.

A bit of hot glue affixed the wood blocks to the inside of the case.

Notches were cut in the sides of the box to handle cabling.

This case is far too big for this project, but I really wanted to make it and this is what I had kicking around .... so tough!

Big Red Switch

This switch is identical to the emergency 'play Black Sabbath' button, and it is mounted on my wall near the heater and some sweet rotary phones. For input, the switch is connected to the Raspberry Pi on GPIO pins 5 and 6.

The Camera

In a few days time, a 5 megapixel camera will be arriving at my home, but for now I'm using this less than ideal 640x480 logitec webcam that has been placed on a shelf next to some sweet rotary phones.

To be honest, everything in my home is "near" a sweet rotary phone (or two).

Nesting in the corner

Oh hey, is that a Raspberry Pi on your wall? Yes, yes it is.

If my home were a Federation Star Ship, that corner would be the Engineering Department. beep boop.

Aside from a bunch of sweet rotary phones, there is lantern that can be controlled over the network, and there is a toaster with a postcard on it. RAD!

Code

So it is built and all in place. What does it do?

The computer is running Arch Linux and the software for this project is written in the Ruby programming language without any third party libraries. However, in order to take a picture, the code makes a system call to fswebcam which is easily installable from the Arch Linux software repositories.

Pressing the big red button takes a pictures and saves the picture in a local directory. The code also contains a basic webserver that makes accessing the saved images a snap. Included in the webserver is a simple way to make the computer take a picture. This can be accomplished by directing a web browser to http://NAME_OF_COMPUTER:8080/capture

Enter the Ruby

#!/usr/bin/env ruby

require 'webrick'
require 'thread'
require 'observer'

#we might be testing
@@TESTING ARGV[0].nil? false true

##define some variables
this_dir File.dirname(__FILE__)
@@public_dir File::join(this_dir,"public")
@@captures_dir File::join(@@public_dir"captures")

class Switch
  include Observable  #instances will be watched!
  def initialize()
    #init the switch GPIO
    @io #this is pin 7 on a Rev 2 Raspberry Pi
    @value_file "/sys/class/gpio/gpio#{@io}/value"
    @value
    #clean up first
    unless @@TESTING
      clean_up()
      File.open("/sys/class/gpio/export","w"do |f|
        f.puts(@io)
      end
      #set direction to in
      File.open("/sys/class/gpio/gpio#{@io}/direction""w"do |f|
        f.puts("in")
      end   
      #record the initial value
      @value get_value()
    end
  end
  
  def get_value()
    unless @@TESTING
      value File.read("/sys/class/gpio/gpio#{@io}/value").chomp()
    end
  end
  
  def run()
    puts "running switch"
    running true
    thread Thread.new() do
      while running
        #poll the value
        value get_value()
        if value != @value
          @value value
          #emit the value
          changed
          notify_observers(self@value)
        end
        sleep 0.1
      end
    end
  end
  
  def clean_up()
    unless @@TESTING
      File.open("/sys/class/gpio/unexport","w"do |f
        f.puts(@io)
      end
    end
  end
end

class PictureTaker
  def initialize()
    'creating picture taker'
  end
  
  def switch_changed(switchvalue)
    if value == "1"
      puts "value: #{value}"
      take_picture
    end
  end
  
  def server_wants_capture(servervalue)
    if value
      take_picture
    end
  end
  
  def take_picture    
    now Time.now.strftime("%Y%m%d%H%M%S")
    puts now
    new_file File::join(@@captures_dirnow+".png")
    cmd "fswebcam -r 640x480 -S 3 -F 2 --no-banner --png 7 --save "+new_file
    #run the command
    start Time.now
    IO.popen(cmddo |f|
      output f.gets
    end
    return (Time.now start).to_s
  end 
  
end

if __FILE__ == $0
  pt PictureTaker.new
  sw Switch.new
  sw.run
  #watch for a switch press
  sw.add_observer(pt:switch_changed)
  #make a webrick server
  server WEBrick::HTTPServer.new({:Port=>8080,:DocumentRoot=>@@public_dir})
  #what server actions do we have?
  server.mount_proc('/capture'do |reqresp
    resp['Content-Type'] = 'text/plain'
    resp.body pt.take_picture
  end

  running true
  trap "SIGINT" do 
    server.shutdown
  end
  
  server.start
  
  sw.clean_up()
  
end

For easier copy and paste, the code is available at http://hoof.jezra.net/snip/ob

What's next?

Aside from waiting for the 5MP camera...

There are still plenty of unused GPIO pins on the computer and there is a lot of unused space in the computer case. It should be possible to find something else to do with the computer.

  1. upload captured images to somewhere on the internet
  2. have the motion detecting toaster tell the picture taker to take (and upload) pictures when motion is detected.
  3. Always skip step 3
  4. enjoy a bit of home security

Now quit reading, and go take a picture.

Comments
2013-06-06 Karl:
Hi mate, I absolutely love this idea, and am thinking of making something similar myself, would it A, be ok to use your stuff and B, should I be able to get it all working using the bits you have listed on here?

great work!

Karl
2013-06-06 jezra:
Hi Karl.
A: Yes
B: Yes

let me know what you create.
2013-12-03 chris:
I really love this example of fun things to do with a raspberry pi. I am fairly new to this stuff and want to reproduce what you have done. What type of button are you using and did you wire it directly from the button connectors to the GPIO pins?
2013-12-03 jezra:
Hi Chris,
I use a fairly generic big red button switch, and just about any button/switch would work for this. There are 2 or 3 pins on the RPi that can have a switch directly connected, and in this case, it is pin 7.
2014-06-27 Jones:
Can you explain me, how I insert the code in the raspberry, please?
I need a programm?
Sorry, i'm noob in this stuff....
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
4 minus 3
Answer:
required
2012-12-24

After setting up my BeagleBone with a phone so that I can run scripts when certain numbers are dialed, it became apparent that I should have other ways of running scripts on the BeagleBone, and most importantly, it should be possible to run scripts at a given time. Time for a new project!

A bit of Background

For a while, I had been using SSH to connect to the BeagleBone and using the at command to schedule the running of a script at a specific time. For example to run my "maiden_fade" script at 8:10 AM, I would use the following:

echo "morning_maiden" | at 8:10

While that is fine and dandy, I really really did not want to open a terminal, ssh to the BeagleBone, and then type a command in order to do what I want. To make everything nice and easy, I started the Ruby Web Alarm Project. Originally, I had wanted to hack this thing together during a "code sprint" at the NBLUG, but there wasn't enough time. Oh well.

What is Ruby Web Alarm?

Ruby Web Alarm is a small Ruby based webserver with an HTML and JavaScript based UI that allows one to schedule scripts to be run within 24 hours. (This should really be changed to allow a user to set a script to run farther in the future)

Screen Shots!

Some Blather About the Code

As stated previously, the server for Ruby Web Alarm is written in Ruby (hey, fancy that!), and does not use any 3rd party gems. All of the included libraries are part of the Ruby Standard Library.

For the UI, jQuery handles async calls to the server and I'm using a slightly modified stylesheet from the MuttonChop Mobile UI.

What it Needs

While the project mosty does what I need it to do, it still needs some feature improvements such as:

  • Better time input (I'd like to use the HTML5 Range element, but none of the modern webkit based mobile browsers have a working Range element)
  • Daily, weekly, monthly repeating

Maiden Fade?

In case you were wondering what the "maiden_fade" script looks like...

#!/bin/sh
curl http://wind:2876/player/volume/0
/opt/mc_random_iron_maiden.py
for i in {1..35}; do
    curl http://wind:2876/player/volume/$i
    sleep 1
done

It sets the volume to zero, starts playing a random Iron Maiden track, and then, each second of the next 35 seconds it increases the volume by 1. Is there a better way to wake up than to fade into some Maiden?

And if you were wondering about the Random Iron Maiden script...

#!/usr/bin/env python2
import urllib
import json
import random
f = urllib.urlopen("http://wind:2876/audio/search/iron%20maiden")
fjson = f.read()
search_result = json.loads( fjson )
files = search_result['files']
random_file = random.choice( files )
url = "http://wind:2876/audio/play/%d" % (random_file['id'])
urllib.urlopen(url)

UP THE IRONS!

Comments
2013-04-10 Alice:
My coder is trying to convince me to move to .
net from PHP. I have always disliked the idea because of the expenses.
But he's tryiong none the less. I've been using WordPress on a variety of websites for about a year and am worried about switching to another platform.
I have heard very good things about blogengine.net.
Is there a way I can transfer all my wordpress content into it?
Any help would be really appreciated!
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 7
Answer:
required
2012-12-01

For some reason, I am drawn to Will-o'-the-wisp even though I have never seen them. Fortunately I have an inclination for breaking things and making things and it seemed obvious that I would need to make my own Will-o'-the-wisp with some hardware and software hacking.

In a nutshell, I wanted to put some LEDs in a lantern and randomize the LEDs brightness with a computer to emulate a flickering effect.

Hardware Used

  • phone cord : because I have plenty of it
  • tin candle lantern : because it was available
  • beaglebone ( I used my wall mounted beaglebone ) : because it can have more than one PWM controlled pin

Will-o'-the-Wisp Hardware

The Lantern

This tin candle lantern was the obvious choice for this project because it has been at my home for a few years and I don't really use it. TAKE IT APART!!!

There was a small tin cylinder in the lantern that held candles. Some gentle twisting quickly removed the holder. This was followed by sanding the inside of the lantern to make it more reflective and then small hole was punched in the bottom of the lantern.

This was my first time working with tin.

Solder some LEDs

Three bright LEDs were soldered to a four wire phone cord. Three wires for power and one wire for ground. The phone line adapter was scavenged from a broken touch-tone phone.

pfffttt touch tone

Hack a Notch in the Box

While putting together this hack, I thought it would be a good idea to include a phone cord jack for the rotary input device as well as the lantern.

Why Cringe?

I have friends that are wood crafters, and whenever they see my wood working skills they cringe a little bit. The easiest solution to the problem is to not show them what I do, but that wouldn't be as much fun.

Two phone cord jacks hot glued into the notch. It looks fairly flush on the top... and functional (which is what I really want).

Move the Beaglebone

While I had the box disassembled, I move the stand-offs closer to the wall of the box so that the beaglebone's USB port can be accessed more easily. I don't know what I'll use the USB port for, but it's nice to know that it is ready when I need it.

Plugged In Phone Jacks

All glued up, the lid closes properly, and I'm happy.

On the left is the lantern cord and the rotary phone is on the right.

LEDs

The LEDs are mounted in the tin lantern with a heavy dollop of hot glue! RAD!

Will-O'-The-Wisp Code

For this project, I opted to use the Ruby programming language because... well... just because. For now, all I need is a bit of File IO to write data to files. One of the reasons that I'm such a fan of the beaglebone is that in most instances, controlling the pins can easily be done simply by writing to a file.

All I really need to do, is have 3 instances of PWM on the Beaglebone and set the value of the pins to a random number in order to emulate a flickering effect.

Enter the Ruby

#!/usr/bin/env ruby

#define the pins that will be used, and their muxing values
#these are:
# 9.14
# 9.16
# 8.13

pins = [
  {:mux_value=>6:mux_target=>"gpmc_a2":pwm_dir=>"ehrpwm.1:0"},
  {:mux_value=>6:mux_target=>"gpmc_a3":pwm_dir=>"ehrpwm.1:1"},    
  {:mux_value=>4:mux_target=>"gpmc_ad9":pwm_dir=>"ehrpwm.2:1"}
]

#a class to represent an LED
class Wisp
  def initializepin )
    @min_val 0
    @max_val 100
    #set the pwm_dir
    pwm_dir File.join("/sys/class/pwm",pin[:pwm_dir])
    #set the pwm duty_percent file
    @duty_percent File.join(pwm_dir"duty_percent")
    @run File.join(pwm_dir"run" )
    #mux the target
    target File.join("/sys/kernel/debug/omap_mux"pin[:mux_target])
    set_file_value(targetpin[:mux_value] )
    #set up the PWM
    set_file_value(@duty_percent0)
    set_file_value(File.join(pwm_dir,"period_freq"), 100
    set_file_value(@run1)
  end

  def clean_up()
    set_percent(0)
    set_file_value(@run,0)
  end
  
  def set_percentvalue )
    set_file_value(@duty_percentvalue)
  end

  def random()
    val rand(@min_val..@max_val)
    set_percent(val)
  end

  def set_min_val(value)
    @min_val value if value >=and value <=100
  end
  
  def set_max_val(value)
    @max_val value if value >=and value <=100
  end
  
  private 
  def set_file_value(filevalue)
    #puts "#{file} :  #{value}"
    File.open(file'w'do |f|
      f.putsvalue )
    end
  end
end

#make an array of wisps
wisps pins.each.map {|pWisp.new(p)}

#we want to create a controllable loop
loop true

#let Ctrl+c break the loop
trap "INT" do 
  loop false
end

#start looping
while loop do
  #give each wisp a random percent
  wisps.each do |w|
    w.random()
  end
  #take a bit of a nap
  sleep 0.1
end

#when the loop is broken, clean up
wisps.each do |w|
  w.clean_up()
end

For easy copy and paste, this code is available at http://hoof.jezra.net/snip/o6.

It should be noted that this code is running on a beaglebone Rev. 3 using Arch Linux.

If I had the hardware, I probably would have used an ethernet cable instead of a phone cord. That way, I could run 7 LEDs instead of 3. Well it works, and that's what really counts. Now I have to start writing the web UI that will allow me to control the LEDs from a web browser. In case you haven't noticed, I like internetted things.

Now quit reading, and go find a will-o'-the-wisps.

Comments
2012-12-09 Alison Chaiken:
Needs a video!
2012-12-10 jezra:
A poorly created video is now available at http://www.youtube.com/watch?v=zNztCHwPolc&feature=plcp
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 3
Answer:
required
2012-10-31

No, I haven't been burning the oatmeal, but while doing some tests for a new project, I did write another network accessible wrapper to the espeak "text to speech" software. This time, I used the Ruby programming language and the webrick Ruby module which makes writing little web servers a snap.

For the longest time, I had a fairly negative opinion of the Ruby language. However, now that I've written some stuff with Ruby, I actually find the language quite pleasant. There are however, still a few drawbacks for me:

  • Rails. Since Ruby on Rails is arguably the flagship Ruby application, searching the web for solutions to ruby issues usually turns up a lot of Rails specific solutions.
  • The Gems package management system. Ruby libraries that are not part of the Ruby core or the Ruby standard library are usually distributed as a "Gem" and installed via the Gem package manager (that always seems to be at odds with my operating system)
  • Some other stuff because I'm a grump who doesn't like anything.

Actually, none of that is a problem with Ruby itself, so I should really just shut my pie hole. Alright, back to the applications.

What I wanted was a web page I could access with any browser and enter text for the host machine to speak, and a URL that I could hit with curl or wget in order to get the host machine to speak text that I send to it.

Oh wait! There is something I really don't like about ruby: the way multi-line comments are handled. blech! OK, rant over. (seriously, go look it up)

Enter the Ruby

#!/usr/bin/env ruby
require 'webrick'

#create the html of the 'index' page
@index_html="<html>
<head>
<title>Time Talky Thing</title>
</head>
<body>
<form target='iframe' action='/speak'>
<label for='string'>Text to Speak:</label><br>
<input type='text' name='text'><br>
<label for='delay_minutes'>Delay Minutes:</label><br>"
@index_html += "<select name='delay_minutes'>"
  (0..100).each do |i|
    @index_html += "<option>#{i}</option>"
  end
@index_html += "</select>"
    
@index_html+="<input type='submit' name='Submit'>
</form>
<iframe name='iframe'></iframe>
</body>
</html>"

#function to run when / is requested
def index (reqresp)
  resp['Content-Type'] = 'text/html'
  resp.body @index_html
end

#function to run when /speak is requested
def speak (reqresp)
  #get data from the request
  text req.query['text']
  delay req.query['delay_minutes'].to_i 60
  resp['Content-Type'] = 'text/plain'
  resp.body "OK"
  speak_stringtextdelay)
end

def speak_stringtextdelay )
  #build a command 
  cmd ""
  cmd += "sleep #{delay&& " if delay
  cmd += "espeak  -a150 -s150 -g7 -ven-us+f3  \"#{text}\"" if text
  #if there is a command, run it in a new thread
  unless cmd.empty?
    Thread.new do
      IO.popen(cmd)
    end
  end
end

# Initialize our WEBrick server
if $0 == __FILE__ 
  config = {:Port => 8000}
  server WEBrick::HTTPServer.new(config)
  #what urls do we need to mount?
  server.mount_proc('/'do |reqresp
    indexreqresp )
  end
  server.mount_proc('/speak'do |reqresp
    speakreqresp )
  end
  
  trap "INT" do 
    server.shutdown 
  end
  server.start
  
end

Code also available at: http://hoof.jezra.net/snip/o5
What's Happening? Run the code and point a browser at NAME_OF_MACHINE:8000 and you will be presented with a basic page with a form and an iframe. The form has the iframe as its "target" and the "/speak" url as its 'action'. This keeps things simple from an end users point of view since the page never needs to refresh.

If I don't want to use the form, I can simply send a command such as

curl HOSTNAME:8000/speak?text=check+the+oatmeal+jackass&delay_minutes=4

to tell me what to do in 4 minutes. ha!

The only downsides to this are:

  1. having to URLencode strings that need to be spoken
  2. the length limit of a GET request

Now I need to run this code on the NaNoBox (and install a speaker in the box).

sweet sauce, another internetted thing. Actually, it would be best to run this code on a computer in a toaster.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
4 plus 5
Answer:
required
2012-10-28

For those that don't know, November is when NaNoWriMo (National Novel Writing Month) takes place and there is a poster on the wall of my work that tracks the progress of my coworker's word count. However, since I work from home quite a bit, the poster in the office doesn't do me much good.... time to create a device that allows me to track people's progress.

Hello NaNoBox!

The basic idea of of the NaNoBox is to use a mini computer to access the NaNoWriMo wordcount API and do the following:

  1. Every hour, get the current word count of a small list of NaNoWriMo participants
  2. Loop through the list of participants and:
    1. illuminate something that represents a NaNoWriMo participant
    2. show the progress of the represented participant as a percentage on an analog meter

The Hardware

Earlier this month I acquired a Raspberry Pi from http://www.adafruit.com, and I felt that the device would be ideal for this project.... I was wrong.

While I still used the Raspberry Pi for my project, I will never purchase another one, nor will I ever use the Raspberry Pi for another similar project.

Why I don't like the Pi

  • There are no mounting holes. When I make a custom enclosure for a computer, I like to mount the motherboard on standoffs, and I can't do this with the Raspberry Pi. lame
  • Handling PWM is not as simple as just writing to a file, and I needed to compile a 3rd party application to help me with PWM. I was spoiled by the Beaglebone
  • The is little to no documentation for the Raspberry Pi at http://www.raspberrypi.org/. Very lame
  • Proprietary video drivers. Not a big deal for this project, but still less than ideal

Alright, that's enough complaining about the computer, let's get on with the build.

The Interface

At the suggestion of my friend who is a wood worker, I made a template of how I wanted the interface of the NaNoBox to look.

The Case

There is something about the scent of a nice cedar cigar box that tickles my brain.

This cigar box has been hanging out at my house for long enough. Time to chop it up.

Chop Chop

After laying out the design on the box, I used my poor selection of power tools to hack a bunch of holes in the cigar box.

The logo on the box was impossible to remove, but I sanded it down as best as I could.

Power

On the back of the box I drilled a hole for the power cable to go through.

Ewww, there is a sideburn hair in the picture

Stain

After much sanding, I stained the top of the box with the only stain I have (other than coffee), some red leather stain.

Diffused

On the inside of the box, I hot glued a piece of paper to diffuse light that shines upon it. What? you'll find out.

DVD Box

This is part of a DVD case. I like the way this plastic clip holds paper in the DVD case and I figure it would be a not too crappy way to hold the Raspberry Pi in place within the box.

Computer Mount

The DVD case clip was cut up and hot glued into the cigar box. See the black dots? LEDs will go there.

Like I said earlier, the lack of mounting holes is a serious downside of the Raspberry Pi (at least for the type of projects I like to do).

Put It On The Wall

Two screws and a bit of baling wire make for a great wall hanging bracket.

LEDs Wired Up

Here are the LEDs, soldered together and hot glued in place so that they shine through the square holes in the cigar box.

Voltmeter In Place

A 3.3V voltmeter was bolted into place, and an amber LED was hot glued over a small hole in the center of the box.

All Wired Up

The electronics are all done and ready to roll! I added a small 802.11n wireless USB adapter so that I wouldn't need another cable running to the computer.

Picture Holdy Thingies

Some thick paper was cut and folder in order to make holders for pictures that will be placed over the openings in the case.

Glued In Place

There it is! Two of the image holdy thingies were glued to the box for each of the images.

All Done

The pictures are in place and ready to light up.

The photographs are of my coworkers. The two other images represent podcasters that I listen too who happen to be participating in this years NaNoWriMo, and they are Thistleweb from Crivins and Moosical from The Bugcast.

Tim Kim

Hi Tim! Wow, I took a very unflattering picture of Tim for use in this project. Haha!

In Action

I dropped some test data into the code that runs the NaNoBox so that I could see the device in action before the event starts. When the participants get to 50,000 words, the amber LED in the middle of the box illuminates.

The Code

For this project, I decided to use Ruby because .... um... why not?

#!/usr/bin/env ruby
require 'open-uri'
require 'rexml/document'

class Pin
  def initialize(num)
    @gpio num
    #init this pin
    `gpio -g mode #{@gpioout`
  end
  def on()
     `gpio -g write #{@gpio1`
  end

  def off()
    `gpio -g write #{@gpio0`
  end

end

class Nanobox
  def initialize()
    #keep it clean
    clean_up()
    #define the pins
    @pins = [
      Pin.new(4),
      Pin.new(17),
      Pin.new(23),
      Pin.new(24),
      Pin.new(25),
      Pin.new(22),
      Pin.new(21),
      Pin.new(10),
    ]
    #create the PWM on 18 (AKA pin 12)
    `gpio -g mode 18 pwm`   
    
  end

  def clean_up()
    #unexport all of the assigned pins
    `gpio unexportall`
  end

  def set_pwm_percentnum )
    max 1023
    offset_percent num*0.92
    val max*offset_percent/100
    `gpio -g pwm 18 #{val}
  end

  def read_config_file
    this_dir File.dirname(__FILE__)
    config File.join(this_dir"users.conf")

    @users = []
    File.open(configdo |file|
      file.each_line do |line|
        if line.chomp != ""
          user = {"name" => line.chomp}
          @users << user
        end
      end
    end
  end

  def update_wordcount_of_user(user)
    begin
      url "http://nanowrimo.org/wordcount_api/wc/"+user['name']
      xml_text open(url).read
      doc REXML::Document.new xml_text
      if doc.root.elements['error'].nil?
        wc doc.root.elements['user_wordcount'].text.to_i/500.to_i
      else
        wc 0
      end
    rescue
      #do nothing
    end
    if wc 100
      wc 100
    end
    
    @users.each_with_index do |ui|
      if u['name'] == user['name']
        user['wc'] = wc
        @users[i] = user
        break
      end
    end
  end

  def update_all_wordcounts() 
    @users.each do |user|
      update_wordcount_of_user(user)
    end
  end

  def run
    #read the config
    read_config_file()
    #do the initial wordcount update
    update_all_wordcounts()
    @running true
    update_wc_count 0
    count 0
    sleep_time #seconds
    update_wc_count_max 3600/sleep_time
    current_pin nil
    while @running
      unless current_pin.nil?
        current_pin.off()
      end
      current_pin @pins[count]
      current_pin.on()
      count+=1
      update_wc_count+=1
      if count >= @pins.length or count >= @users.length
        count 0
      end
      if update_wc_count update_wc_count_max
        update_wc_count 0
        update_all_wordcounts()
      end
      value @users[count]["wc"]
      if value >= 100 
        @pins[7].on()
      else
        @pins[7].off()
      end
      set_pwm_percent(value)
      sleep 5
    end
    quit
  end
  
  def stop
    @running false
  end
  
  def quit
    set_pwm_percent(0)
    #turn off all of the LEDS
    @pins.each do |p|
      p.off()
    end
    #do some cleanup
    clean_up()
  end
end

if __FILE__ == $0
  nano Nanobox.new
  trap("INT"){ nano.stop }
  nano.run()
end

Yup, that's some ugly, uncommented code. While parsing the XML that gets returned by the Word Count API, I realized that I don't like processing XML in Ruby, and then I had an epiphany: XML is very consistant, I dislike parsing it in any language. ZING!

Wait a second. Don't I have more coworkers? Why aren't they represented in this project? Yes, I do have more coworkers, but the cigar box has a very limited amount of space. Fortunately, I have enough components to make another NaNoWriMo status tracker. Time to get to the drawing board.

Now quit reading, and get ready to start procrastinating.

Comments
2012-11-28 d-r:
Mr. Jezra -

You probably are already aware of this, but the latest rev of the Raspberry Pi has mounting holes (just got mine today!).

Also, have you checked out Adafruit's modification of Raspbian, called Occidentalis? They've done some stuff to make the GPIO stuff more useful, including PWM. I don't really understand that stuff, but perhaps a real hacker such as yourself will find it useful: http://learn.adafruit.com/adafruit-raspberry-pi-educational-linux-distro/occidentalis-v0-dot-2

It doesn't sound like they intend to maintain it like a real distro, but it looks useful for single purpose projects.

Maybe some day they'll get around to proper documentation . . . seems like a genuinely useful open graphics driver isn't going to happen though.

Regards,

d-r
2012-11-28 jezra:
Is it still only two holes? 3 points make a plane, 2 points make a line.

The idea of installing as full Desktop distro just to get decent PWM support is not for me. I'll stick with the BeagleBone
2012-11-28 d-r:
Good point, it is just two holes. It seems like there's a real need for a barebones distro optimized for hardware hacking on the RPi . . . maybe someone will take Adafruit's modifications and turn it in to a real distro.
2012-11-28 jezra:
it's called "Arch"
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 3
Answer:
required
2012-05-23

Scattered about my home are lists of things to do. The first thing to do on quite a few of these todo lists is: Find the old todo list

Since I would sometimes spend more time looking for a missing list than I would spend actually doing something on the list, putting things to do on a piece or paper is a very inefficient way to keep track of what I need to do.

Fortunately, one of my federated status.net buddies told me about MyTinyToDo; a sweet todo list that one can install on their own server if they happen to know how to do such things. As it happens, I have such skills.

A few minutes of tinkering and a new subdomain later, my todo list went from being a scattered collection of paper to a nice tidy website that I can access from almost anywhere. awesome sauce!

But how will I know how many things are on my todo list if I don't have the list opened in a browser window? Good question, I'm glad I asked it.

MyTinyToDo can be configured to provide an RSS feed of a list and I decided to get a binary representation of the number of unfinished items on the list from the RSS feed. A short Ruby script does this quite well.

Enter The Ruby

#!/usr/bin/env ruby

#we need to read from a uri
require 'open-uri'
#we will need to parse xml
require 'rexml/document'

uri "http://todo.jezra.net/feed.php?list=1"
begin
  #read the xml data from the feed
  xml_data open(uri).read()
rescue
  puts "failed to read text from #{uri}"
  exit
end

#parse the XML
doc REXML::Document.new(xml_data)

uncompleted_item_count 
#loop through the "items" in the feed
doc.elements.each('rss/channel/item'do |i|
  uncompleted_item_count+=unless /Completed: / =~ i.elements['description'].text
end

binary =  "%b" uncompleted_item_count
leds binary.reverse.ljust(6,"0")
p leds

For more information about the machine that displays the number of items on the list, check out http://www.jezra.net/blog/LEDs_BeagleBone_and_my_ToDo_List

Now I need to find a missing key. Rock on, and go make some copies of your keys.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 minus 0
Answer:
required
2011-12-29

I'd Rather Not Have To Write This

Hey! Where is the blog post about moving hosting providers? Good question. Lamentably, the answer is "pilot error", A.K.A. it is all my fault. During a code push to my new hosting provider, I accidentally clobbered my new database access config file with my old config file. Fortunately, the end result of my mistake, was the loss of just one blog post.

So What Happened?

When the config was clobbered, all database requests pointed to my old hosting provider and when my service time with them expired, they removed my former database from their systems.

As a result, any information that was added to the database after the clobber was lost (with the exception of the RSS PHP post which I still had on my computer in markdown format). sql

So What Was Lost?

The decision making criteria that I used when selecting http://www.webfaction.com as my new hosting provider and http://gandi.net as my new domain registrar is gone. I'll do a quick recap:

webfaction

  • multiple websites on one hosting account
  • shell access
  • runs web apps and not just scripts

How about I make this nice and short and just send you to their features page

Gandi.net

And who could forget the loss of information about http://googlebutter.com? Not me, that's for sure. Hi Buddy!

What Am I Doing to Limit Loss in the Future?

The real culprit here is me for not making enough database backups. So I had better write a script to make database backups for me. For security reasons, my new provider does not allow mysql connection from other than localhost or 127.0.0.1, I need to:

  1. Create an SSH tunnel to my new host
  2. Run a mysqldump and pipe the output to a text file
  3. tar gzip the text file

Enter the Ruby

#!/usr/bin/env ruby
require 'date'
require 'open3'
#create variable for database access
db_server "MYSERVER_IP_ADDRESS"
db_user "MY_DATABASE_USER"
db_pass "MY_DATABASE_USER_PASSWORD"
#what day is it?
Ymd Date.today.strftime("%Y%m%d")
sql_file "#{Ymd}_dump.sql"
#create an ssh tunnel
sshcmd "ssh -L 3307:127.0.0.1:3306 #{db_server-N"
dumpcmd "mysqldump -A -u#{db_user-P3307 -h127.0.0.1 -p#{db_pass#{sql_file}"
#start the sshcmd
puts  "starting ssh tunnel..."
Open3.pipeline_start(sshcmddo |threads|
  #sleep for a bit and give the thread time to connect
  sleep 10
  #get the first thread
  threads[0]
  puts "ssh tunnel has pid: #{t.pid}"
  #run the dump command
  puts "dumping database info"
  system(dumpcmd)
  puts "data dump is complete, killing ssh tunnel pid #{t.pid}"
  #kill the ssh tunnel
  Process.kill("TERM"t.pid)
end
#tar the sqlfile
tarcmd "tar -czvf #{sql_file}.tgz #{sql_file}"
system(tarcmd)
system("rm #{sql_file}")

For easy copy paste, the code is in the hoof http://hoof.jezra.net/snip/nT

What I should really do, instead of tunneling the mysqldump and then tar gzipping the data on my machine, Is perform the dump and archive on the server and then transfer the compressed archive to my machine.

Now quit reading, and go backup your data! (Then go see my buddy at http://googlebutter.com) Hi Buddy! hahaha

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
7 minus 2
Answer:
required
2011-10-16

Quite often I find myself over at http://thesession.org checking out old traditional tunes. The site offers the tunes as sheet music and ABC Notation. While I certainly appreciate having ready access to the tunes, I find that, more often than not, I need to copy the ABC notation into a text file, convert the text file into a [lilypond[(http://lilypond.org) file, transpose the lilypond file to the G Major scale, and then finally add some code that will display the tune as banjo tabs. Oh man, did you catch all of that? There has to be an easier way to do this.....

Oh, there is...

For the most part, the process is just a series of text manipulations which can be accomplished in just about any programming language. Since I am currently on a Ruby kick, I figured I would just hammer out a quick script to automate the process of retrieving ABC notation from a given page and converting the results into something more usable.

Enter the Ruby

#!/usr/bin/env ruby

require 'optparse'
require 'open-uri'
require 'rexml/document'
require 'iconv'

def sanitizestring )
  #replace non alpha-numerics with _
  string string.gsub(/[^A-z0-9_]/,'_')
  return string.downcase
end

def errorstring )
  puts "**** ERROR ****"
  puts string
  exit
end

option = {}
OptionParser.new do |opts|
  opts.banner "Usage: sessionconvert [options] [thesession.org_URL]"
  
  opts.on("-b""--banjo""add banjo tabulature"do |v|
    option[:banjo] = v
  end
  
  opts.on("-l""--lilypond""compile with lilypond"do |v|
    option[:lilypond] = v
  end
  
end.parse!

url =  ARGV[0]
# was a url supplied?
unless url
  error "you must supply a thesession.org url"
end

begin
  text open(url).read
rescue
  error "Failed to read #{url}"
end



begin
  #watch out for sneaky 
  ic Iconv.new('UTF-8//IGNORE''UTF-8')
  valid_string ic.iconv(text)
  doc REXML::Document.new valid_string
rescue
  error "Failed to parse #{url}"
end

root doc.root
root.elements["body/div[@id='abc']/div[@class='box']/p"].to_s
abc p.gsub(/<.*>/,'')

#get the title
/T: (?<title>.*)/.matchabc )
title m['title']
#get the key
/K: (?<key>.*)/.matchabc )
key m['key']

#sanitize the title, we will use this a bunch
sanitized_title sanitize(title)
#what will the filenames be?
filename_abc sanitized_title+".abc"

open(filename_abc,'w')
f.write(abc)
f.close

begin
  `abc2ly #{filename_abc}`
rescue
  error "abc2ly is not installed"
end

#what will the lilypond file be?
filename_ly sanitized_title+".ly"

if option[:banjo]
  #what is the key we are transposing from
  t_key key[0].downcase
  banjer_bit "\\transpose  #{t_keyg,
    \\context TabStaff = \"banjer\"{
    \\set TabStaff.stringTunings = #banjo-open-g-tuning
    \\set Staff.instrumentName = #\"Banjo\"
    \\context TabVoice = \"banjer\" { \\tabFullNotation \\stemDown \\voicedefault }
  }"

  text File::read(filename_ly)
  #substitute the banjer stuff
  text text.gsub(/<<.*>>/m"<<\n #{banjer_bit}\n\t>>")
  #make a new lilypond file
  filename_ly sanitized_title+"-banjo.ly"
  #write the new text to file
  open(filename_ly,'w')
  f.write(text)
  f.close
  
end

#did the user request compiling 
begin
  `lilypond #{filename_ly}if option[:lilypond]
rescue
  error "lilypond is not installed"
end

If you have problems copying the code, please see http://hoof.jezra.net/snip/nQ

Usage

Since I don't always want to convert a tune to banjo tabs (some tunes are better on the concertina), a flag (-b) needs to be included with the arguments to specify that the tune should be converted to banjo tabs. Similarly, including the -l flag with compile the final lilypond file into a pdf of sheet music or tabs.

For example: The tune Rakes of Mallow is found at http://www.thesession.org/tunes/display/85 and to convert the tune to banjo tabs and compile to a PDF the following command would be used.

./sessioncovert.rb -bl http://www.thesession.org/tunes/display/85

This is what the final PDF looks like. Damn that's nice!

...and this is me butchering the tune on my banjo. Damn, I need more practice.

Awesome! Now quit reading, and go butcher a tune on the instrument of your choice.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 3
Answer:
required
2011-09-19

A few years ago, I decided to rip and organize my entire CD collection. The resulting music files are all on player a small computer, running MPD, and connected to my home stereo. It took me a while, but it was worth it. In a similar fashion (since I am somewhat stuck at home recuperating) I figured now would be a prime time to rip my entire DVD collection. To accomplish this feat, I am using a piece of software called handbrake.

More specifically, I am using the command-line version HandBrakeCLI with a small bit of Ruby code.

The Code

#!/usr/bin/env ruby
require 'open3'
input '/dev/sr0'
title ARGV[0]

#what are the video options?
v_opt "-r 29.97 -b 1200 -2 -T"

#what are the audio options?
a_opt "-E lame -B 128 -R 48 --mixdown stereo"

#what are the source options?
source "--main-feature"
command "HandBrakeCLI -i #{input} #{source} #{v_opt} #{a_opt-o #{title}.mp4"

puts command

Open3.pipelinecommand )

Why the Ruby code?

the Ruby code takes one argument: the name of the movie, and creates a string representing the command I want to use for ripping the DVD.

So then one might ask why not just use the graphical version of handbrake? Good question, I'm glad you asked.
I don't just run the Ruby script ( by the way, I named the script 'hbwrap.rb' ). The script is run with a few other scripts as well. Remember the text to speech thingy from a while ago? Well I run that as part of the command as well.

The process is as follows:

  1. Put Carts of Darkness into the DVD drive of my bedroom computer.
  2. On the command line, run: hbwrap.rb Carts_of_Darkness && ttsender "finished ripping" && eject
  3. Walk into living room and do some work
  4. Wait until the stereo says "finished ripping"
  5. Walk into the bedroom and repeat the process
  6. ?
  7. Contemplate purchasing a much much larger hard-drive to hold all of my movies
  8. Think of a way to playback the movies that is controllable by an NES controller
  9. Write a blog post about the process

Yes, I have already ripped Jaws the Revenge. By accident, I almost ripped The Princess Bride, but I decided to wait until I have time to edit out all of the useless Fred Savage crap that ruined the film. In case you were wondering, The Princess Bride is one of the few instances where the book is worse than the film.

Now quit reading, and go do something something something.

Comments
2011-09-21 jrobb:
nifty, I don't think I have used handbrake before. So this doesn't look like it bypasses that lame DVD protection stuff, does it?

(isn't it legal now to bypass this to backup your own collections and whatnot?)
2011-09-21 jezra:
From what I can tell, my ripping endeavor falls under Fair Use.
2011-09-21 jrobb:
right, yeah it definitely does.
I *think* what I was saying is that I thought handbrake was just a transcoder, not a ripper+transcoder.

it's awesome that it is both!
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 4
Answer:
required
2011-07-27

While hacking code and introducing bugs to a project, I sometimes find it necessary to search for a string in every file in a directory. For example, suppose I'm pulling my hair out trying to find where in a drupal project a certain function is called. It could be in a module, or a theme, or the template file. Egads! the code is all over the place and I most certainly do not want to open every damn file and search for the function.

I had been using a script called 'find_in_dir' to help me find a string in a directory and the script is as follows:

find $1 -type f -exec grep -i $2 {} \; -print

The usage of the script was simple:
[jezra@fatlappy ~]$ find_in_dir PATH_TO_SEARCH STRING_TO_FIND

The script would recursively search each file in the directory and output the lines in a file containing the search string and then output the path to the file that was searched. While this definitely did what I needed, I found that I was spending too much time tracking down the path to the file because all of the output was just plain text on the screen.

What I really wanted/needed was something to differentiate the file path in the output from the rest of the output, so I fired up geany and hacked together some Ruby code to do what I need (and output the file path using green text. ohhhhh fancy!)

Enter the Ruby

#!/usr/bin/env ruby

#make a helpy helper function 
def help()
  print "--USAGE--\n"
  print "findindir directory string_to_find\n"
end

#find (
#loc: path to a file or directory,
#string: the string to search for
#)
def find(locstring)
  #is the location a directory?
  if File.directory?(loc)
    #loop through each item in the directory
    Dir.foreach(locdo |name|
      #ignore . and ..
      if name!='.' and name!='..'
        #what is the path of the item?
        path File.join(loc,name)
        #recurse
        find(pathstring)
      end
    end
  else #this is a file 
    #by default, we state that there is no match to the string
    match false
    #loop through each line in the file
    File.foreach(locdo |line|
      #does the line contain our string?
      if line.include?(string)
        match true
        puts line
      end
    end
    #a match was found
    if match
      puts  "\e[32m#{loc}\e[0m\n\n"
    end
  end
end

#set some vars based on user input
start_loc ARGV[0]
string ARGV[1]

#did the user enter enough data?
if start_loc.nil? or string.nil?
  help
else
  #determine the absolute path to the start location
  path File.absolute_path(start_loc)
  #find that shit!
  find(pathstring)
end

Having trouble copying the code? get it at http://hoof.jezra.net/snip/6

The code isn't perfect and could certainly use some improvements, such as:

  • ignore broken symlinks
  • output the line number where matches were found
  • default to current working directory when the user doesn't specify a directory

It may not be the best, but it's a start. Now quit reading and go find something.

Comments
2011-07-27 Kevin Granade:
This get's you most of the way to your solution:
find $1 -type f -exec grep -i $2 {} \; -printf "[%p]\n" | colorit

It has the side effect that it might also colorize other parts of the results, and I didn't quickly see a way to safely remove the brackets after colorization.
2011-07-27 jrobb:
very cool, jez. I'll look through this, ruby seems pretty interesting to me.
2011-07-27 Matt Platte:
It's traditional to condescendingly break out the awk or sed one-liners here but for me, I get some joy - perverse joy, no doubt - in building something with the "wrong tools" that gets the job done anyhow. Good on you, Jezra
2011-07-28 jezra:
If it gets the job done, is it really the wrong tool? Possibly; but this way I'm learning ruby (which is the real goal)
2011-08-15 x1101:
@Jezra you're learning ruby to add another 'wrong' tool to your belt?
2011-08-15 jezra:
A tool is a tool; and the language is growing on me.
2011-09-15 Daniel Kullmann:
grep does that already (although not in green):

grep -ir --color=always pattern directory

the -r recurses through all subdirectories, and --color=always colorises the output.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 6
Answer:
required
2011-03-07

The Need

For part of a Ruby project I've been working on, I needed a way to play audio files, and since I am using the project to teach myself the basics of Ruby programming, I decided to write my own audio player class utilizing the Gstreamer Multimedia framework.

Having written what is essentially the same code in both Vala and Python programming languages, I thought this would be fairly simple with the aid of the documentation.

RTFM (a rant)

The documentation for using Gstreamer with Ruby is part of the Ruby Gnome2 documentation and I found it to be dreadful to use. Since it is difficult to actually find a link to the Gstreamer related documents, I'll include a link http://ruby-gnome2.sourceforge.jp/hiki.cgi?Ruby/GStreamer

Honestly, I tried to read the documentation and it was so frustrating that I started to hate Ruby. What bothered me the most about the documentation wasn't the abundant amount of missing information, it was the 500 Server Error that I would see every 4 out of 5 clicks. Why someone thought it would be a good idea to server the files as a CGI wiki and not as good old static files is beyond me. Aaahhhhhhh! I hate that crap!

OK, time to relax and just look at some code.

Enter The Ruby

#!/usr/bin/env ruby
require 'thread'
require 'gst' #gem install gstreamer
#the gst namespace is Gst
#initialize gst
Gst.init
class Player
  def initialize()
    #create a thread for a glib main loop
    thread Thread.new() do
      @mainloop GLib::MainLoop.new
      @mainloop.run
    end
    #make a few queries
    @query_position Gst::QueryPosition.new(Gst::Format::TIME)
    @query_duration Gst::QueryDuration.new(Gst::Format::TIME)
    #make the playbin
    @playbin Gst::ElementFactory.make("playbin")
    #get the playbins bus
    bus @playbin.bus
    #watch the bus for messages
    bus.add_watch do |busmessage|
      handle_bus_messagemessage )
    end
  end

  #we will need to get the current position and duration of the playbin 
  def position_duration()
    begin
      #run the query
      @playbin.query@query_position )
      #what is that, picoseconds? I'll take milliseconds thank you.
      position position @query_position.parse[1] / 1000000
      @playbin.query@query_duration )
      duration @query_duration.parse[1] / 1000000
    rescue
      position 0
      duration 0
    end
    return {'position'=>position,'duration'=>duration}
  end

  def status()
    #get the state
    bin_state @playbin.get_state
    #isn't there a better way to convert the state to a string?
    case bin_state[1]
      when Gst::State::NULL
        state 'NULL'
      when Gst::State::PAUSED
        state 'PAUSED'
      when Gst::State::PLAYING
        state 'PLAYING'
      when Gst::State::READY
        state 'READY'
    end
    volume @playbin.get_property("volume")
    uri @playbin.get_property("uri")
    pd position_duration()
    #return state, volume
    status_hash = {'state'=>state'volume'=>volume'uri'=>uri}
    #add the position and duration to the hash, and return
    return status_hash.mergepd )
  end

  #set or get the volume
  def volume(val)
    if !val.nil? and val>=and val<=1
      @playbin.set_property("volume"val
    end
    return @playbin.get_property("volume")
  end
  
  def seek_percent(val)
    if !val.nil? and val>=and val<=1
      pd position_duration()
      duration pd['duration']
      if duration 0
        seek_loc val*duration 1000000
        seek Gst::EventSeek.new(1.0Gst::Format::Type::TIMEGst::Seek::FLAG_FLUSH.to_i Gst::Seek::FLAG_KEY_UNIT.to_iGst::Seek::TYPE_SETseek_loc Gst::Seek::TYPE_NONE, -1)
        @playbin.send_event(seek)
      end
    end
    return position_duration()
  end
  
  def quit()
    @playbin.stop
    @mainloop.quit
    #I thought no one liked a quitter?
  end
  
  def set_uri(uri)
    #null the playbin state
    @playbin.set_state(Gst::State::NULL)
    #set the uri
    @playbin.set_property("uri",uri)
  end
  
  def play()
    #really? just play
    @playbin.play
  end

  def pause()
    #really? just play
    @playbin.pause
  end

  def handle_bus_messagemessage )
    case message.type
      when Gst::Message::Type::ERROR
        #null the pipeline
        @playbin.set_state(Gst::State::NULL)
        #TODO: send a signal that playing is finished

      when Gst::Message::Type::EOS
        #null the pipeline
        @playbin.set_state(Gst::State::NULL);
        #TODO: send a signal that playing is finished

      when  Gst::Message::Type::TAG
        tag_list message.parse()
        #we need to get the key and value from the tag
        tag_list.each do |key,val|
          #TODO: store some of this data
        end

      when Gst::Message::Type::STATE_CHANGED
        state @playbin.get_state
      else
        #what should we do?
    end
    #return true or shit breaks: why is this? 
    true
  end
end

if \_\_FILE__ == $0
  input ARGV[0]
  if input.match(/^http:\/\//
    #why the hell doesn't this work?
    uri input
  else
    uri "file://"+File.absolute_path(ARGV[0])
  end
  player Player.new 
  player.set_uri(uri)
  player.play()
  loop true
  sleep 1
  while loop  
    puts "type 'quit' to quit"
    $stdin.gets.chomp
    if s.eql? "quit"
      loop false
    end
  end
  player.quit()
end

For some reason the code will not play an audio file over HTTP and this bothered me for a bit, then I decided that I just don't care. One thing you may notice is that this class will create a new thread for running a GLib mainloop. Had this class been part of a larger project that uses a GLib mainloop, the new thread probably wouldn't be necessary, but hey, I'm not writing a GLib based project.

Comments
2011-05-29 rekado:
re: HTTP streaming:
You may want to use "playbin2" instead of "playbin".
2012-05-18 DaKaZ:
Thanks, I found this as a great start. Switching to playbin2 does indeed get http streaming working. I also updated the class to have an equalizer (see below) for those that are interested. What is not working for me and I can't seem to really find any documentation about it is the TAG processing. GStreamer never seems to send and TAG messages, but as far as I can tell the playbin2 bin handles this by default. I wan to get Artist and Title information out of the stream. An example is: http://streampoint.radioio.com/streams/56/47bf578c13be7/ If you play this in mplayer,you'll see all the ICV tags. How do I get those in ruby-gst?

Thanks again for the start here, hope you enjoy the equalizer.

First: add this to initialize()
bin = Gst::Bin.new()
@eq = Gst::ElementFactory.make("equalizer-10bands")
autosink = Gst::ElementFactory.make("autoaudiosink")
bin.add(@eq)
bin.add(autosink)
@eq >> autosink
eqpad = @eq.get_pad("sink")
gpad = Gst::GhostPad.new("gpad", eqpad) # playbin2 requires a ghost pad, not sure why
bin.add_pad(gpad)
@playbin.audio_sink = bin

Then add this function to the class:

#set or get the equalizer, pass in a hash of one or more bands { :band0 => 10, :band1 => 5, ... :band9 => 10}
def eq(bands = {})
b = {
:band0 => @eq.band0,
:band1 => @eq.band1,
:band2 => @eq.band2,
:band3 => @eq.band3,
:band4 => @eq.band4,
:band5 => @eq.band5,
:band6 => @eq.band6,
:band7 => @eq.band7,
:band8 => @eq.band8,
:band9 => @eq.band9
}.merge(bands)
@eq.band0 = b[:band0]
@eq.band1 = b[:band1]
@eq.band2 = b[:band2]
@eq.band3 = b[:band3]
@eq.band4 = b[:band4]
@eq.band5 = b[:band5]
@eq.band6 = b[:band6]
@eq.band7 = b[:band7]
@eq.band8 = b[:band8]
@eq.band9 = b[:band9]
return b
end
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 9
Answer:
required
2011-02-25

The Need

Today I started putting together a little image gallery for my sideburns and I really wanted a way to easily scale a directory of images to a specific height, save the result in a directory and then repeat the process and save in yet another directory.

The Solution

There is probably an easy way to do what I want using GIMP, but I thought it would be better if I wrote a script to handle this for me. Technically, the script that I wrote is just a fancy wrapper for the imagemagick convert command.

Enter the Ruby

#!/usr/bin/env ruby

#create a function to end the script with a message
def exit_help(message=nil)
  #did a message get passed in?
  if !message.nil?
    #print the message
    puts message
  end
  #print the 'help'
  puts "--Help--
#{$0path/to/image/directory thumbnail_height image_height  
  "
  exit()
end

#make a function to create a dir if it doesn't exist
def check_dir(dir)
  if !File.directory?dir )
  Dir.mkdir(dir)
  end
end

#make a function to run a set command
def do_command(comm)
  puts comm
  system(comm)
end

#set some variable  based on the input from the user
start_dir ARGV[0]
thumb_height ARGV[1].to_i
image_height ARGV[2].to_i

#if any of the vars aren't set, the user didn't input enough info
if start_dir.nil? or thumb_height==or image_height==0
  exit_help()
end

#does the start dir exist?
if !File.directory?start_dir )
  exit_help"#{start_dirdoes not exist" )
end

# define the output dirs
gallery_dir File.join(start_dir,'gallery')
thumb_dir File.join(start_dir,'gallery_thumbs')

# make the dirs if they don't exist
check_dir(gallery_dir)
check_dir(thumb_dir)

#loop through the files in the start_dir
start_dir_files File.join(start_dir,"*.{png,jpg}")
Dir.glob(start_dir_files)  do |file|
  #what is the name of the file?
  fname File.basename(file)
  #what is the path of the new gallery file?
  gfile File.join(gallery_dir,fname)
  #what is the path of the new thumbnail file?
  tfile File.join(thumb_dir,fname)
  #what imagemagick commands will resize the images? 
  c1 "convert -resize x#{image_height} #{file} #{gfile}"
  c2 "convert -resize x#{thumb_height} #{file} #{tfile}"
  #run the commands
  do_commandc1 )
  do_commandc2 )
end

What It Do?

I'll tell you what it do! When run, the script requires three arguments: 1. The name of the directory containing the images 2. the height of the thumbnail image 3. the height of the gallery image

The end result will be a directory named "gallery" and a directory named "gallery_thumbs" in the images directory. Gallery will contain the images resized to the gallery height specifications. I'll leave it to you to figure out what images are in the "gallery_thumbs" directory.

My Take Away

Originally, I had planned on using the exec() function to run my convert commands but exec() replaces the calling script with the command being called. This means that as soon as I ran exec(), my code would quit running. Oh well, system() works just fine for my needs.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 3
Answer:
required
2011-02-10

The Need

When I'm away from home, I often find it rather useful to connect to my home network. Due to the dynamic IP address assigned by my Internet Service Provider, I use a third-party service to map a domain name to my dynamic address. This scenario requires that a machine in my network notifies the third-party service of network address changes; and a script to do this was fairly easy to write.

The Preparation

The first thing I need to do was create an account with a dynamic IP Domain Name Service provider. Previously I had use http://dyndns.com but their service has been intermittent for the last few days so I though a switch to http://no-ip.com was in order.

Aside from providing Dynamic DNS, No-IP provides software allowing Linux, Macintosh, and Windows computer users to easily update their dynamic address information. Even more importantly, No-IP provides an API for programmers to utilize when writing their own software to update address information.

The Script

Honestly, this could be done with a single line curl command, but that wouldn't be fun and it wouldn't help me learn Ruby. Enter The Ruby

#!/usr/bin/env ruby

require 'open-uri'

#set up some variables
"MYUSERNAME"
"MyPaSsWoRd"
"my.hostname.com"

url "https://dynupdate.no-ip.com/nic/update?hostname=#{h}"

#open the url
open(url,
  "User-Agent" => "Jezra's No-IP Update Script",
  :http_basic_authentication => [u,p]
)

#output the return data
puts f.read()

This script is now happily running on my miniserver at 4 hour intervals.

The big take away here for me, was learning how to use open() from the open-uri module to set the User-Agent of a request as well as utilizing http basic authentication.

One More Thing

Since I'm learning Ruby, and since I learn by doing, I force myself to write (semi)useful utilities for everyday task, like creating a password for my No-IP account. Instead of just coming up with something I'll remember, like 1 2 3 4 5, I decided to write a string generator. woohoo!

#!/usr/bin/env ruby

def help()
  puts "--Hey, you are doing it wrong--
#{$0requires a numeric argument 
examples:
# create a 20 character string
#{$020
# create a 10 character string
#{$010

"
end

#what chars do we have to pick from?
chars "!@$%^*_+=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

#get the first argument as an integer
num ARGV[0].to_i

#the num better exist, and it better not be zero!
if num.nil? or num.zero?
  help()
  exit()
end

charslen chars.length
string  ''
(1..num).each do 
  char charsrand(charslen) ]
  string << char
end

puts string

Sweet! Now quit reading, and go learn something new.

Comments
2011-02-11 jamba:
pretty cool. how are you liking ruby so far?
2011-02-11 jezra:
I dig it. The standard library comes with buckets of useful modules and I *seem* to get what I need done fairly quickly
2011-02-12 James:
Hey, if you're digging Ruby you might check out http://www.sinatrarb.com/ It's a pretty sweet DSL for Ruby web development. I tend to use it for simple API stuff.
2011-02-12 jezra:
I already have and I plan on using sinatra and thin server to add a web interface on my next project
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 5
Answer:
required
2011-02-06

Hello Ruby

Recently, I've been looking into the Ruby Programming Language and like most programmers I started by writing a "Hello World" program. Now let me say that I am not a fan of most Hello_World programs simply because I don't think they really give much information to the viewer of the code, so I thought I should share the code that I wrote.

There were a few programming language features that I thought should be included in my code.

An Object Oriented class

Quite a bit of my coding is Object Oriented and since most anything I program in the future using Ruby will be Object Oriented, this was a must.

A class that extends the functionality of the previous class

Writing my own classes is good and dandy, but very often I need to extend a base class from and included code library with additional functionality and I wanted to see what sort of coding requirements Ruby has for extending classes.

some sort of array thingy

Each programming language has various names for ways to store a collection of data: list, dictionary, hash table, array, and a few other things that I usually forget because I don't pay much attention to nomenclature. Anyway, I always end up having to program some way to deal with a list of words or numbers or something, so I made sure to include an array in my program.

Enter the Ruby

#!/usr/bin/env ruby

#define a class to print out some words
class PrintThing
  def initialize
    #we need a variable to hold the words to be printed
    @words = [] #use an empty array
  end
  
  #define a function to set the @words
  def set_words(words)
    @words = [words]
  end
  
  #define a function to output all of the elements of the words array 
  def print_words()
    puts @words.join" " )
  end
end

#extend the printthing class to be more advanced
class AdvancedPrintThing PrintThing
  #define a function to add an element to the words array
  def add_wordword )
    @words.pushword )
  end
  
  #define a function to truncate the words array
  def clear()
    @words = []
  end
end

if __FILE__==$0
  #create an instance of the PrintThing
  pt PrintThing.new
  #set some words
  pt.set_words"Hello World" )
  #print the words
  pt.print_words()
  #create an instance of the AdvancedPrintThing
  apt AdvancedPrintThing.new
  #set some words
  apt.set_words'Howdy Buddy!' )
  #print the words
  apt.print_words()
  #clear the word list
  apt.clear()
  #add a bunch of single words
  apt.add_word'How' )
  apt.add_word'are' )
  apt.add_word'you?' )
  #print the words
  apt.print_words()
end

What I learned

  • Defining a class is easy and a new class should have a constructor method named "initialize" if the class needs to do something when a new instance of the class is created
  • Extending a class can be done with the "<" symbol, so to code that class A extends class B is as simple as "class A < B".
  • a variables scope is determined by the variable's starting character.
    $ for global variables
    @ for instance variable
    @@ for class variables
    While this can look confusing at first, I like being able to determine at a glance exactly what the scope of a variable is.
  • There was no need to worry about code indentation. Ahhh relief.
  • Ruby does not create byte-code when an ruby program is run. sigh of disapproval

All things considered, I like the language and there is some rather impressive documentation available at http://www.ruby-doc.org/

Now quit reading, and go find yourself a programming project.

Comments
2011-04-10 senshikaze:
Have you checked out "why's poignant guide to ruby"? It is hilarious at least. chunky Bacon!

http://mislav.uniqpath.com/poignant-guide/book/chapter-1.html
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
4 plus 8
Answer:
required
subscribe
 
2019
2016
2015
2014
2013
2012
2011
2010
December
November
October
September
August
July
June
May
April
March
February
January
2009
December
November
October
September
August
July
June
May
April
March
February
January
2008