One more snow "storm," one more chance to take snow related pics. This time, Shira even got in the on action taking the first two photos.
Thursday, February 26, 2015
Wednesday, February 25, 2015
Adventures in Dithering: Making some gray in a sea of black and white
Yesterday I implemented support for printing images in my DropPrint Android app. One issue with the printer is the range of values it prints: mainly it has none. As they say: you can have any color you want, as long as it's black. So a typical photo, which is filled with all sorts of grays, gets turned in a photo filled only with black and white. Like this one:
In some cases this effect may be desirable, but I was curious if I could leverage the halftone effect to simulate shades of gray. With a halftone you trick the eye into seeing gray by by varying the mixture of white and black dots. One Google search told me that while halftoning may get the job done, there's another important option to consider:
If you are doing this because you like the effect, that's cool. But if you just want to dither down to a black and white image consider using a "Floyd-Steinberg" dither. It provides high quality results and distributes the error across the image. http://en.wikipedia.org/wiki/Floyd-Steinberg_dithering
The Floyd–Steinberg dithering looked exactly like what I wanted, and the Wikipedia page even gave me an algorithm I could translate to scheme code:
(for-each-coord src-w src-h (lambda (x y) (let* ((old-pixel (rgb->gray (pixels (idx x y)))) (new-pixel (if (> old-pixel 128) 255 0)) (quant-error (- old-pixel new-pixel))) (store! x y new-pixel) (update! (inc x) y (* quant-error (/ 7 16))) (update! (dec x) (inc y) (* quant-error (/ 3 16))) (update! x (inc y) (* quant-error (/ 5 16))) (update! (inc x) (inc y) (* quant-error (/ 1 16))))))
After fixing update! so it wouldn't try to update pixels outside of the image (I'm looking at you (-1, 1)), I was surprised at the quality of the image generated. Here's the same image as above, but with dithering in place:
Look at that, there's now some "gray" in the image!
I'm not sure what to make of the white vertical bars. They are almost certainly a defect in code as I've printed other images that don't have them.
The main issue I have with this algorithm is that it's terribly slow. Dithering a 384x447 pixel image takes almost 30 seconds, with the vast majority of that time spent looping over every pixel in the image. I'm sure I'm doing something inefficient, though it's possible that I'm running into a performance issue with Kawa Scheme. At some point, I'll probably debug it further and see why it's so slow.
Next up: I've got to see if I can get rid of those annoying white bars and then I need to make the Bluetooth connectivity far more bullet proof. When that's done,I should have a pretty dang functional app.
As usual, the DropPrint source code can be found here.
Tuesday, February 24, 2015
Gotcha of the Day: CORS Requests fail when switching to an HTTPs Environment
Tonight I was migrating a client's site from HTTP to HTTPs when I ran into this conundrum: when I updated www.foo.com to invoke api.foo.com over HTTPs I started getting CORS related errors in Firebug:
This is confusing because api.foo.com was and is setup to return back the appropriate CORS related headers. Surely CORS isn't supposed to just break when you switch to HTTPs, is it?
I checked and double checked the various CORS headers. Then I came across this notation in the specification:
The term user credentials for the purposes of this specification means cookies, HTTP authentication, and client-side SSL certificates that would be sent based on the user agent's previous interactions with the origin.
Perhaps by switching to HTTPs I now needed to follow the instructions related to user credentials including:
If the resource supports credentials add a single Access-Control-Allow-Origin header, with the value of the Origin header as value, and add a single Access-Control-Allow-Credentials header with the case-sensitive string "true" as value.
Otherwise, add a single Access-Control-Allow-Origin header, with either the value of the Origin header or the string "*" as value.
The string "*" cannot be used for a resource that supports credentials.
Alas, making sure that Access-Control-Allow-Origin was set to the exact value of Origin and sending Access-Control-Allow-Credentials: true made no difference.
Finally, through little more than dumb luck I visited https://api.foo.com in my browser. I'm working in a dev environment, so no big surprise, I got the usual Certificate warning:
Naturally, I accepted the certificate.
And to my complete shock and amazement, the problem was fixed!
This kind of sort of makes sense. I can see why Firefox wouldn't allow CORS requests from a certificate that wasn't (a) valid or (b) added to the exception list. But man, they could have saved me quite a headache if they had somehow indicated the issue in Firebug. At the end of the day, this wasn't really a CORS issue in that CORS was perfectly setup.
DropPrint 2.0: Image and QR Code Support
This morning I finished up the next iteration of DropPrint, a tiny Android app that drives a thermal bluetooth printer. The big improvements: DropPrint now supports images as well as bar codes.
Check it out:
The image printing protocol for the printer I'm using, a DL58, is pretty dang simple. It consists of little more than sending each row of an image with the following format:
0x1F 0x10 NN 0x00 B1 B2 B3 ...
Where NN is the number of bytes being sent to represent the line of the image. B1, B2, etc. are bytes containing the relevant bits (0 black, 1 white) for each pixel. In other words, if my image was 1 pixel high and 8 pixels wide (I know, not a particularly interesting image):
Black Black Black White White White Black White
I'd send this as:
0x1F 0x10 0x01 0x0 [00011101]
It through me off at first, but that binary header (0X1F 0x10 ...) is sent at the start of each row, not just at the start of the image.
This whole mapping of image pixels to individual bits, followed by compressing the bits into bytes, was an interesting exercise to say the least. Not being much of a hardware interface guy, these are usually details I'm not worrying about. An interesting side effect of printer's binary protocol is that the height of the image is effectively irrelevant. All the printer knows about are the individual rows of the image. I'm using this to my advantage in DropPrint by rotating images that are taller than they are wider. The result is that especially narrow or wide images, like a panoramic shot, actually do well on the printer.
One obvious issue with the printer is that it only prints black and white, there's no ability to send any sort of gray pixels. The result is that your typical photograph, filled with not-quite-black-not-quite-white areas, looks awful when printed. Still, there's hope. The notion of the halftone was invented to solve exactly this problem. Invented back in the 1830's, it's hardly new tech. The idea is to simulate gray by interspersing black and white dots. Just like our brains can be fooled into seeing fluid movement using the principles of animation, we can also be fooled into seeing gray when only black and white is present. Anyway, this is an area I'll be coming back to.
With the basic image printing ability out of the way adding QRCode support was quite easy. I grabbed the zxing library and put it it to work. Now when DropPrint discovers a .qrc file, it encodes the text found within as a QR Code and prints that.
Next up: I'll work on improving the image printing quality as well as improving how the app handles being put in the background and losing connection to the printer. Still lots to do, but it's amazing when this guy prints out an image I've sent to it.
Check out the source code here.
Monday, February 23, 2015
Nifty emacs trick: impatient-mode for instant buffer sharing
It may only only be 10:20am, but I'm willing to bet this is going to be the coolest thing I see all day. Perhaps all week. Lifted from this post: Emacs: Peer-to-peer coaching is easier when you use impatient-mode to share your buffer I typed the following into emacs:
M-x package-install impatient-mode M-x httpd-start C-x b foo M-x impatient-mode
I then opened up: http://localhost:8080/imp/ and clicked on foo. As I typed in emacs, my browser automagically updated:
(Yeah, that static screenshot doesn't do the functionality justice.)
I have no idea how I'm going to put this capability to work. But the fact that it just magically works leaves me in awe.
Emacs just never ceases to amaze.
Just a Little Impossible: Morris Counting
I'll often advise entrepreneurs I talk with that it's ideal if their Software Idea is just a little impossible. ProgrammingPraxis recently published a challenge / solution that fits this description well. It's quirky, but still instructive. Here's my own word-problem based description of the challenge:
Imagine you're given the task at counting entrants to the state fair (yum!). Your boss hands you one of those crowd counter devices and walks away. As you examine the counter you realize that it only counts up to 255. Once the 256th person walks in, your screwed. The counter won't work anymore.
What do you do? Pray that the state fair has 254 attendees? Flee for your life? If you're Robert Morris, you get creative and devise a new way of counting, one that solves this seemingly impossible problem.
Here's what you do: you borrow a coin from your fellow fair employees and you stand by the gate. The first time someone walks in, you click the counter. Now it reads one. The next time someone walks you, you look down at the counter and flip the coin however many times is shown on its face. If your coin comes up heads every time, then you click the button to increment the counter. And repeat.
So if the counter says 8, then you have to flip the coin 8 times in a row. And if you get heads 8 times (unlikely, but do it enough, and it'll happen), you increment the counter to 9.
When your boss comes by and asks how many people visited the fair you bust out your pocket calculator compute 2x-1, where x is the number shown on the counter.
Of course, this won't be the exact number of people who visited the fair, but it will be in the ballpark. It'll certainly tell you if there were 10's, 100's or 1000's of visitors that day. And that's far better data than having nothing.
Here's some random executions of the this algorithm:
In some cases, the number is pretty accurate (524 was estimated at 511, 242 was estimated at 255). In other cases, it's pretty out there (2956 vs. 4095). But still, considering that you're making use of nothing more than a very limited counter and a single coin, the results are quite impressive.
The bigger lesson though is the recipe at play here: find a problem which others think is impossible, solve it, and you're on your way to changing the world. That's not too much to ask, is it?
Here's the code that implements the above algorithm:
;; ;; http://programmingpraxis.com/2015/02/20/morris-counting/ ;; (define (show . words) (for-each display words) (newline)) (define (heads? n) (let ((flip (= 1 (random-integer 2)))) (cond ((not flip) #f) ((and flip (= n 1)) #t) (else (heads? (- n 1)))))) (define (int-count n) (+ 1 n)) (define (morris-count c) (cond ((= c 0) 1) ((heads? c) (int-count c)) (else c))) (define (morris-value c) (- (expt 2 c) 1)) (define (trial upper) (let loop ((n (random-integer upper)) (i 0) (c 0)) (cond ((= n 0) (show "actual=" i ", morris=" (morris-value c))) (else (loop (- n 1) (int-count i) (morris-count c)))))) (define (test) (for-each trial '(10 50 100 200 500 800 1000 1500 2000 5000 7000 10000)))
Thursday, February 19, 2015
Screenshots for the Lazy Linux User
One of tools I used most frequently on Windows isthe Snipping Tool. This relatively tiny program allows you to grab a screenshot and crudely annotate it. There are fancier versions out there, but the Snipping Tool has been built into Windows long enough that you can just depend on it.
So what program should I use to take screenshots on Linux? One solution I found and immediately liked was the peculiarly named scrot. scrot is your typical command line app, so it doesn't do a whole lot (which is ideal!). You can type:
scrot
and it just grabs a screenshot of the current screen. Or you can dress it up a little bit by adding a delay (which lets you get the right window position) or use -s flag to select a region to snapshot from your current screen.
The "problem" with scrot is that once I'm done with an image, I like to typically annotate it. And for that, I typically use Gimp. So while using scrot by itself is handy, it's not particularly efficient.
Changing gears a bit, it occurred to me that I could just grab a screenshot from within Gimp. That works well enough, but I was curious if I could streamline the process. As is, I found myself navigating to the File » Create menu, clicking on the 'Grab' button, and then switching the foreground color to red so that I could annotate the image in a clear way. Rather than perform these steps manually, I thought I combine them into a bit of script-fu:
(define (bs-snip-it) (let ((image (car (plug-in-screenshot RUN-INTERACTIVE 0 0 0 0 0 0)))) ; [XXX] (gimp-context-set-foreground '(255 0 0)) image)) (script-fu-register "bs-snip-it" "Snip-It" "Grab a screenshot and be ready to edit it" "Ben Simon" "Copyright 2015, Ideas2Executables, LLC" "2015/02/18" "") (script-fu-menu-register "bs-snip-it" "<Image>/File/Create")
I have to admit though, I still wasn't satisfied. To grab a screenshot I needed to switch to gimp, kick off this plugin, and interact with the screenshot dialog. I thought I could improve this by running plug-in-screenshot with RUN-NONINTERACTIVE. However, there appears to be a bug in the C code that keeps this from properly working. And regardless, having to switch to Gimp in the first place is a pain.
So on to version 3.0 I went. I ended up combing both scrot and Gimp, driving the whole thing with a shell script:
#!/bin/bash ## ## grab a screenshot and open it in gimp ## sleep .2 img=$HOME/Pictures/screenshots/`date +%Y-%m-%d-%H%M`.png (echo "Select area to screen shot" | festival --tts) & scrot -s $img startgimp $img sendgimp "(gimp-context-set-foreground '(255 0 0))"
I wired this script into ratpoison with the following line in my .ratpoisonrc:
definekey top s-g exec snipit
Now, when I hit Windows-g (Windows is the Super key, hence the s- prefix) the above script is executed. The sleep .2 is required by ratposion, without it the script doesn't execute properly.
The first order of business of the script is to read aloud some instructions to me. Then it kicks off scrot -s $img which allows me to select just the area of interest and saves it into an image.
From there, startgimp is executed, which as you'll see below is a thin shell script wrapper around Gimp. Gimp already has the nice feature that if you hand it an image and there's an instance running, it won't start a new instance, but will use the existing one. The next line sends a bit of code to gimp telling to set the foreground color to red.
This means that I hit one keystroke, select the area of interest, and Gimp pops up in front of me, ready to annotate away. So for now, I'm happy with my screenshot capability.
The scripts below, startgimp and sendgimp leverage the Script-Fu server built into Gimp. When this server is running it's possible to send Gimp arbitrary commands, essentially scripting it from the command line. startgimp kicks off Gimp, insuring the Script-Fu server is running, and sendgimp sends script-fu code to Gimp using the correct protocol. These scripts should be reusable outside of the screenshot context. Essentially, I now have command line scripting of Gimp, to go along with its built in plugin based architecture.
Here are those scripts:
# ######################################################################## # startgimp # ######################################################################## #!/bin/bash ## ## Kick start gimp off just the right way ## SERVER_LOG=$HOME/.gimp-2.8/server.log exec gimp -b "(plug-in-script-fu-server 1 \"127.0.0.1\" 10008 \"$SERVER_LOG\")" "$@" # ######################################################################## # sendgimp # ######################################################################## #!/bin/bash ## ## Send gimp all the expressions on the command line. Or, if ## no command line, then read from stdin ## SERVER_HOST=127.0.0.1 SERVER_PORT=10008 function gimp_send { expr="$1" len=`echo -n $expr | wc -c | cut -d ' ' -f1` (perl -e "print pack('an', 'G', $len)" ; echo $expr) | nc $SERVER_HOST $SERVER_PORT } if [ -z "$1" ] ; then expr=`cat` gimp_send "$expr" else while [ -n "$1" ]; do gimp_send "$1" shift done fi
Wednesday, February 18, 2015
The Next Best Thing to a Fleet of Personal Reconnaissance Drones
Yesterday the DMV was hit by a "major snow storm" that shut down the area. As the new day began I was curious what conditions were like around the area. But how to know? Turn on the news and trust the Main Stream Media? Don't think so.
No, I wanted to get a first hand view of the situation, and I wanted to do it while sipping hot tea in my PJs. So it was off to the Internet!
TrafficLand has a DC area map that allows you to view the area traffic cameras. It's this kind of data I was after. But manually selecting each camera feed is just so tedious. I'm on Linux baby, surely I can do better than hunting and pecking on a Google Map.
The first thing to note is that if you right mouse click on any traffic cam, you can access an image URL that looks like this guy:
http://ie.trafficland.com/660/full?system=trafficland-www&pubtoken=defa0f069743edb951716d02aa17111bf26&cache=56104
Assuming you have the feh image viewer (sudo yum install feh), you can grab and view this image like so:
curl -s 'http://ie.trafficland.com/660/full?system=trafficland-www&pubtoken=defa0f069743edb951716d02aa17111bf26&cache=56104' | feh -. -
That's already an improvement over the Google Maps UI, but we can do even better. Next up, if you've got ImageMagick installed (sudo yum install ImageMagick), then you've got the montage command at your disposal. montage combines images into a sort of old school contact sheet. So if we can grab multiple camera images we can trivially combine them into one combined image.
Another item worth noticing is that the above URL contains a parameter named pubtoken. That value almost certainly expires after a few minutes, meaning that if you hard code the above URL in a script, it's going to stop working after some time.
So what's the right way to get that camera URL with a fresh pubtoken? Well, it's not particularly obvious. But by examining the web traffic under Firebug, it's also not that hard to figure out. At the end of the day, the page in your browser needs a valid URL, and your script is simulating the web browser, so it needs to simulate this step as well.
With those kinks worked out I ended up with the script printed below. Now when I type trafficcams I get an image like the following to pop-up:
That's a montage of 6 cameras that interest me. I came up with the camera ID's (see below) by examining the URL mentioned above.
I suppose one could ask if what I'm doing is appropriate from an ethical stand point. That is, is it OK to lift image data off TrafficLand without using their UI? I'm far from qualified to answer this question, but I will suggest this: we're used to thinking of web browsers as being Internet Explorer, Chrome or Firefox. But the reality is, a web browser is any tool that allows you to, well, browse the web. If you were blind, you'd expect your web browsing experience to be audio based. If you're Linux user, and lazy, then why not have your web browser experience cut right to chase? The agreement has always been that a web browser requests content from a site, that site returns it and it's the job of the browser to render this content. I've just decided that I'd like to render the TrafficLand site a little differently than most users.
It certainly wouldn't be appropriate to grab the image data from TrafficLand and sell it, or utilize it in a project without their permission.
I mention all this because I think it's easy to get stuck of thinking of the web browsing experience as sort of like watching TV. All TVs show the same rendering (though, quality obviously differs) of the same content. But, on the web, we're not limited to that narrow mindset. Make your tools work for you!
OK, enough chatter. Here's that script:
#!/bin/bash cache=`echo $RANDOM` base_dir=/tmp/tc.$$ cams="401807 200003 930 401770 200113 640" mkdir -p $base_dir for c in $cams ; do url=`curl -s "http://www.trafficland.com/video_feeds/url/$c?cache=$cache"` echo -n "Grabbing: $c ... " > /dev/stderr curl -s "$url" > $base_dir/$c.jpg echo "Done" > /dev/stderr done dest=$1 ; shift if [ "$dest" = "-" ] ; then output="cat -" else output="feh -. -B black - " fi montage $base_dir/*.jpg -geometry '400x400>' - | $output rm -fr $base_dir
Our Snow Day Experience in One Photo
This photo pretty much perfectly captures our snow day experience yesterday. What you can't tell is that: (a) I promised Shira we would shovel together, (b) when she said it was time to shovel I told her I'd be joining her in just a few minutes (that is, as soon as some code I was working on was finished) (c) I lied about (b), (d) when I finally did show up, Shira was 97.9% done witih Shoveling, and (e) I promised I'd help her finish up.
And then you have this photo:
You can almost hear me saying: "whoa! check out those icicles. Let me grab some photos and *then* I'll shovel."
If it makes Shira feel any better, I was always good at getting out of shoveling growing up, too.
Monday, February 16, 2015
Gotcha of the Day: Google Docs API Suddenly Starts Returning 401 Unauthorized
Some time back I created a tool that uses the Zend Gdata Spreadsheets API to pull data in from various spreadsheets and store it in a system database. The code has been rock solid. That is, until today, when attempts to access one particular Google doc resulted in this error:
HTTP 401 Unauthorized
Yeah, that wasn't a heck of a lot to go on. Not only that, but nothing obvious had changed: the document in question was still shared with the user trying to access the doc, and the user credentials hadn't been updated. So why the lack of authorization?
My first thought was that it had something to do with the automatic migration Google promised for older docs. Perhaps this one particular document had been migrated, and in doing so, the spreadsheet key had been altered. If that was the case, the existing code may very well be expected to fail (as it looks up the spreadsheet by this unique ID).
At first I thought I was on the right track: the Google Doc in question did have an updated URL. But alas, putting in the new spreadsheet key made no difference. The system still got an unauthorized message when it tried to access the document.
Next up, I tried using a different set of user credentials. Again, no change.
Then I started picking apart the Zend API calls. I'm essentially executing this code:
$service = Zend_Gdata_Spreadsheets::AUTH_SERVICE_NAME; $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service); $spreadsheetService = new Zend_Gdata_Spreadsheets($client); $query = new Zend_Gdata_Spreadsheets_DocumentQuery(); $query->setSpreadsheetKey($spreadsheetKey); $feed = $spreadsheetService->getWorksheetFeed($query); // Failing here
I updated the above code to print out just a list of possible spreadsheets. The code was something along these lines:
$service = Zend_Gdata_Spreadsheets::AUTH_SERVICE_NAME; $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service); $spreadsheetService = new Zend_Gdata_Spreadsheets($client); $feed = $spreadsheetService->getSpreadsheetFeed(); var_dump($feed);
I finally caught a break here and was pleased to see that the spreadsheet in question was indeed found in the list =Google was returning to me.
So this proves that the user can see the spreadsheet, yet he still can't access it.
After spending more time digging around the Zend code I finally took an altogether different tact.
The Google Spreadsheet API, at least accessing the list of worksheets, is actually quite simple. You can cobble together client to accomplish this using little more than curl. So that's what I did. I followed this guide and manually executed the URL requests to authorize my user and access the spreadsheet in question. Again, to my surprise, it worked.
So what was I doing manually with curl that the Zend API wasn't doing? Hmmm...looking closely I realized that the Zend request to pick up the spreadsheet was using http. the curl example was, you guessed it, using https. I opened up Zend/Gdata/App.php and scrolled down to around line 640 and added this line of code:
public function performHttpRequest($method, $url, $headers = null, $body = null, $contentType = null, $remainingRedirects = null) { ... // Make sure the HTTP client object is 'clean' before making a request // In addition to standard headers to reset via resetParameters(), // also reset the Slug header $this->_httpClient->resetParameters(); $this->_httpClient->setHeaders('Slug', null); $url = str_replace('http', 'https', $url); // [ADDED] // Set the params for the new request to be performed $this->_httpClient->setHeaders($headers); $this->_httpClient->setUri($url); $this->_httpClient->setConfig(array('maxredirects' => 0));
That line of code blindly re-writes the URL from 'http' to 'https' -- and what do you know, that solved the problem!
I can't really blame this on Zend. I'm probably using version 1.x of their API, and they have version 2.3.
My guess is that it was the spreadsheet upgrade that ended up biting me. On the surface the change Google made was pretty tiny: you must now access Google Docs over HTTPs (versues having HTTP and HTTPs both work). That change, however, was enough to throw a monkey wrench into my code.
In hindsight, requiring docs be accessed over HTTPs makes perfect sense. Though I would have appreciated a clearer error message when I violated this requirement. With that said, it sure was nice to re-familiarize myself with the Google Docs API and how it can be accessed from the command line. Who knows, maybe I'll find a way to integrate this in with my other Linux hacking projects.
Friday, February 13, 2015
Mobile Bluetooth Printing, Linux Command Line Edition
I recently picked up a DL58 mobile Bluetooth thermal printer and got it working with Android. I'm on a Linux kick, so I was curious if I could get the device to work from there.
The first step was to discover and pair the printer with my Linux box. There seem to be a number of different tools and methods for doing this. I found that the basic pairing process was easily accomplished using bluetoothctl. Though, it wasn't quite obvious where to go from there.
I made one leap forward when when I discovered that I could connect the rfcomm device to the printer, using something like this command:
sudo rfcomm connect DL58 00:14:01:03:5A:26
When I did this, the printer chirped to life and printed out these lines of text:
Wait a second, those are AT commands--I recognize them from my old modem days. I read this as follows: the printer was reporting that I had a serial connection to it and it was awaiting the proper set of AT commands.
I went back to the eBay seller: did they have these AT commands documented? Alas, they didn't. Well that's too bad.
I put the printer down for a couple days and tried again. This time I happened to run an lsof and found that a program, ModemManager, had the rfcomm device open. Wait a second: I was reading the above conversation all wrong. The system saw rfcomm device come on line and ModemManager sent it some AT commands to try to get it configured. The printer, being a printer, printed the commands.
I disabled ModemManager by running sudo systemctl disable ModemManager. I then rebootd the system and ran the following commands:
sudo hciconfig hci0 up sudo hcitool scan sudo rfcomm bind DL58 00:14:01:03:5A:26 sudo chmod a+rw /dev/rfcomm0
I was then able to execute:
echo Hello World > /dev/rfcomm0
And printer spat out Hello World. Success!
Because the printer follows one of the core principles of Linux (treat everything like a file), it's trivially to script. For example, I can enter the following to print a quippy statement:
(fortune -n 50 ; echo ; echo ; echo) > /dev/refcomm0
The -n keeps fortune from generating an especially long message, and the extra echo's serve to advance the paper.
Here's a slightly more realistic example: Shira's headaches are often tied to changes in barometric pressure. I created the script below to capture the current pressure in our area. I can then kick off the following command line:
bpressure > /dev/rfcomm0 ; \ while sleep 20m ; do bpressure > /dev/rfcomm0 ; done
The result is an old timey ticker tape of barometric pressure data. Here's one run:
I'm telling you, this Linux stuff is addictive. Could I have written the above code on Windows? Probably. Would it have taken me a matter of minutes, definitely not. I'm still not 100% sure I'll find a use for the DL58 printer attached to my Linux box, but I'm super impressed at how easy it is to leverage it.
My bpressure script which prints out the current barometric pressure:
#!/bin/bash ## ## Print out our barometric pressure ## LAST_FILE=$HOME/.bpressure touch $LAST_FILE pressure=`curl -s 'http://api.openweathermap.org/data/2.5/weather?q=Arlington,VA,USA&units=imperial'| jq .main.pressure` last=`cat ~/.bpressure` scale() { echo "scale=0 ; $1 * 1000" | bc | sed 's/[.].*//' } if [ -z "$last" ] ; then sym='!' else now_value=`scale $pressure` last_value=`scale $last` if [ $now_value -eq $last_value ] ; then sym='=' elif [ $now_value -gt $last_value ] ; then sym='^' else sym='v' fi fi echo $pressure > $LAST_FILE echo "$sym $pressure"
Thursday, February 12, 2015
A Man, His Guitar and One Entertaining Night
You know how they say that if you rent a tux twice, you were better off just buying one? Well, I missed my chance to test this theory out with cowboy boots. See last night I attended my second country music concert in 10 days. This one was for the great Travis Tritt. He's actually a contemporary of Garth Brooks, but instead of seeing him with 19,000 of my closest friends, it was a cozy night at the Birchmere. And while Garth had a band, a "wall of sound" and special effects, Travis just had himself and a couple of acoustic guitars.
Good golly that man can sing. And tell a story. It was thrilling seeing and hearing a country music legend live and up close.
Mark my word, next country music concert I go to, I'm at least wearing plaid (I don't think I'm ready to rock the hat or boots). Check out a few photos and sound clips. Good times!
Thermal Nuclear Bluetooth Printing on Android (minus the nuclear part)
Inspired by this photography idea, I picked up a thermal Bluetooth printer from eBay. Specifically, a model DL58. I was impressed with the Chinese company that sold it to me; my request for access to the SDK was immediately and fully met. While I've got big plans for the printer, the first order of business was to get it printing basic text.
I don't really have any experiencing developing with Bluetooth devices, so I wasn't quite sure what I was in for. Thankfully, the device was discovered and paired with my phone without a hitch (pairing PIN: 1234). The first version of the SDK the eBay seller provided had the printing code wrapped up behind a shared native library. When I couldn't get this to work, the eBay seller provided an updated version of the code. This time, I could see that all the system was doing was making a Bluetooth socket connection to the device and sending text over the socket. Well that's easy enough. The printer does handle images too, and those are sent using some sort special binary header. That's something I'll be figuring out soon enough.
I then went to work taking my new found lessons in Bluetooth printing and turning them into a super simple app. I give you: DropPrint. DropPrint watches the directory /sdcard/DropPrint and when new files are discovered it prints them. Right now, DropPrint only understands two types of file: txt and scm files. I plan to also have it support image files, as well as bar code files (store "foo" in foo.qrc, and a qrcode with the contents "foo" get printed).
There's not much to DropPrint, but here's the requisite screenshot:
DropPrint's handling of txt files is obvious enough. But surely you're wondering what a scm file are all about. Well, it is what you think it is: Scheme file handling.
See, I implemented DropPrint in Kawa Scheme. That makes it trivial to read, eval and literally print scm files. For example, I can drop the following code in DropPrint/fib.scm:
(begin (define (dec x) (- x 1)) (define (fib x) (cond ((= x 0) 1) ((= x 1) 1) (else (+ (fib (dec x)) (fib (dec x)))))) (let loop ((i 0)) (cond ((= i 10) 'done) (else (display i) (display " = ") (display (fib i)) (newline) (loop (+ i 1))))))
A few moments later, the printer spits out the following:
(Beware: it intentionally deletes the files after printing them)
In the bits-and-bytes world I live in, to actually have something physical generated is amazingly cool.
As implementation languages go, I'm very much warming up to Kawa. It's certainly far more satisfying to use for this sort of project than say PhoneGap, which has a huge number of layers to get to (HTML, JavaScript, PhoneGap code and core Java Code to name a few) before you can work with core Android APIs. I'm not yet sure I can make the case that Kawa is generally better for app development then Java itself, though, with time I'm sure I'll get there. I'm certainly finding it a heck of a lot of fun to program in.
Now that I've got basic printing working, it's time to turn my attention to images. Stay tuned...
Wednesday, February 11, 2015
What a Difference 17 Years Does and Doesn't Make
Shira posted a fairly recent snapshot of ours up next to this classic one:
Here, let's take a closer look:
We were about 17 in the old photo, and it was snapped about 17 years ago.
You can date the old photo relatively easily, thanks to the digital watch on my right hand. That disappeared my senior year when Shira gave me the watch I still wear (when I wear a watch at all, which isn't often). Also of note, while I've lost the friendship bracelet, I have kept the friend who gave it to me. And I'm sure one of those photo-analysis experts on CNN would have a field day with this photo, because if you look closely, Shira's arm isn't around me--she's holding the fence. An obvious sign that this couple isn't meant to last.
Fortunately for me, that analysis would have been wrong. Whoo!
Monday, February 09, 2015
Typing on Windows, Making Magic on Linux - Adventures in Linux Desktop Remote Control
I always keep two laptops in rotation and every Monday I toggle back and forth between the one I'm using. At the moment, one runs Windows, the other Linux. Last week I spent on the Linux box, this week it's Windows time.
As an aside, one of the trickiest aspects of setting up Linux was learning that my touchpad wasn't properly recognized (it needed to be marked as a touchpad). Basic operations like dragging windows and selecting text wouldn't work; it was terrifically frustrating. This morning, like an attention seeking toddler, my Windows box decided that it too should have touchpad problems. It stopped working, and caused the keyboard to go haywire. I've since turned off the trackpad, and the system appears to be functioning. Guess Windows isn't always the Just Works OS I claimed it was.
OK, back to the task at hand.
I'm typing on my Windows box, but my Linux box is right next to it. I can reach over, and type plsplayer http://somafm.com/bootliquor.pls to start playing some background music. But this is Linux, surely I can do better than that? Of course I can.
The first order of business was to get the ssh daemon running on my Linux box. This turned out to be easily done with these commands. Next I needed to figure out how I was going to learn the IP address of my Linux box. I was imagining the need to run some sort of internal DNS. Turns out, it's far easier than that. My router, upon giving my Linux box its IP address, noted its hostname. To my complete shock and amazement I can do:
ssh longshot ls
Where 'longshot' is the name I gave my Linux box. It just works. I knew the router provided DNS services, I never imagined it was smart enough to incorporate names from DHCP into this service. Note to self: don't underestimate the router.
OK, so I can now ssh to my Linux box. This is good. But it gets better: because I'm relying ratpoison as my window manager, and because ratpoison is scriptable I can have it switch windows around remotely with great ease. I just type:
ssh longshot DISPLAY=:0.0 ratpoison -c next
I can trivially kick of my voice commands, too:
ssh longshot saytime
By default, Firefox will open up the URL handed to it within currently focused instance. So the following opens up CNN in the current browser:
ssh longshot DISPLAY=:0.0 firefox http://cnn.com
And for complete control, I can use xdotool to simulate mouse and cursor events. I can bring up a word definition by using the following set of commands:
ssh longshot DISPLAY=:0.0 firefox http://google.com ssh longshot DISPLAY=:0.0 xdotool key Return ssh longshot DISPLAY=:0.0 xdotool type definition ssh longshot DISPLAY=:0.0 xdotool key space ssh longshot DISPLAY=:0.0 xdotool type their ssh longshot DISPLAY=:0.0 xdotool key Return
Or, perhaps I want to bring up cnn.com and then page down the screen:
( ssh longshot DISPLAY=:0.0 firefox http://cnn.com ; sleep 5 ; ssh longshot DISPLAY=:0.0 xdotool key Next ; sleep 5 ssh longshot DISPLAY=:0.0 xdotool key Next )
Note that xdotool type sends text, whereas xdotool key sends a particular key.
Of course, typing all this in by hand is silly. I whipped up a quick shell script which I run from Cygwin on my Windows box. There's almost certainly some fancy keyboard / screen sharing solution I could setup between these laptops. But that's not really what I'm after. I love exploiting the power of the command line, and using the above tools I can do that even if the command line I'm typing at happens to be on a different computer.
Note: none of the above commands X-related commands will work for you unless you do something like xhost + in your X-session. You almost certainly don't want to do this, though, a it allows anyone on your local network to broadcast something to your screen. Back in college, if a TA or perhaps even professor happened to do this, we'd make them pay by doing something like:
xv -display 192.183.282.47:0.0 -root foo.jpg
and in the middle of their lecture and foo.jpg would become their new background. Yeah, not something you want to allow a bunch of kids in a lab, two buildings away, to be able to do.
Anyway, think through how you want to share access to your screen before you do.
Here's that script:
#!/bin/bash ## ## Run commands on my Linux box who happens to be named ## longshot. ## HOST=longshot DISPLAY=:0.0 RP=ratpoison cmds="saytime|sayforecaste|xeyes|nextwin|prevwin|ff|pgdn|pgup|sh|vol" cmd=$1 ; shift expr2='' case $cmd in saytime) expr="$cmd" ;; sayforecast) expr="$cmd" ;; xeyes) expr="$cmd" ;; nextwin) expr="$RP -c 'next'" ;; prevwin) expr="$RP -c 'next'" ;; ff) expr="firefox $@" ;; pgdn) expr="xdotool key Next" ;; pgup) expr="xdotool key Prior" ;; sh) expr="$@" ;; vol) expr="amixer set Master $1" ;; *) echo "Usage: `basename $0` {$cmds}" exit ;; esac ssh $HOST DISPLAY=$DISPLAY $expr if [ -n "$expr2" ] ; then ssh $HOST DISPLAY=$DISPLAY $expr2 fi
Sunday, February 08, 2015
Blame it all on my roots--I didn't show up in boots. But it was still an awesome time. Go Garth!
Perhaps the most impressive part of the Garth Brooks concert Shira and I hit last night with good friends was this photo:
I know what you're thinking: it's a line of people waiting to enter the venue, what's the big deal? Thing is, this is the line of people waiting to enter Garth's *second* show of the night.
That's right, after putting on an amazing 2 and half hour show for what appeared to be a sold out crowd, he was going to turn around and do it again in about an hour. That is after putting on two shows the previous night. But that's so Garth. He's definitely larger than life, with people going berserk for pretty much everything he played (OK, the response to his new song was a little less enthusiastic).
As a bonus, his wife, Trisha Yearwood sang a couple of songs, too. Man, I love her voice.
After the concert we had a delicious Japanese dinner at Station Square, followed by a trip up the Monongahela Incline funicular (built in 1870!) to take in a spectacular view of Pittsburgh. Great friends, great music, great food and a chance to take photos of the city; I was in heaven.
This morning on our way out of town we stopped at The Zenith for brunch. The Zenith is an antique store / vegetarian restaurant (or as one Yelp reviewer called it, Crackerbarrel for hipsters), and the food was quite good. The dessert selection, included as part of brunch, was especially impressive.
What a fantastic weekend adventure!
And here are some sound clips:
Friday, February 06, 2015
All the Info, None of that Pesky Reading - Adding Audio Widgets to X-Windows
Slowly but surely, I'm optimizing my new Linux environment. So far, I'm loving ratpoison as a front end experience. Sure, I love the clutter free experience and fast keyboard access, but I'm also enjoying how easy it is to configure and script. It's definitely a classic Unix tool that shines because it works well with other tools. One catch though I've found is that some of that "clutter" that ratpoison dispenses with does occasionally come in handy. For example, having a clock visible is often useful.
Ratpoison has this particular need covered with with the time command. Hitting Control-t a prints the current and date in the upper right hand corner. If you leverage ratpoison -c "echo SOMETEXT" you can actually convince ratpoison to display any message you want.
But c'mon, this is Linux. We've got to be able to better than that. So I decided this was the perfect use-case to leverage text-to-voice capability that comes with a typical Linux box. Combined with ratpoison's key shortcuts, I've now got quick access to all sorts of pieces of information. Best of all, my fingers don't have to leave the keyboard and my eyes don't have to search out a new location on the screen.
I've put these scripts together over the last couple days, with each one taking about 10 minutes to write. Here's the relevant lines from my .ratpoisonrc, which includes the list of scripts:
definekey top s-t exec saytime definekey top s-f exec sayforecast definekey top s-h exec sayheadlines definekey top s-i exec sayinbox
Note that s-f stands for hitting the "Super" key followed by "f." On my keyboard hitting the Windows key is the equivalent of the Super key. Because, by default, nothing is bound to the Windows key, I've got the entire keyboard open to assign scripts to. Whooo!
Now onto the scripts:
saytime - Actually, I didn't write this script. It comes with festival and just works out of the box.
sayforecast - To hear the current weather in my location I query openweathermap.org. They provide a JSON API which didn't require any sort of sign up. I leveraged the jq command line tool, which makes manipulating JSON from the command line a breeze. This script, like all the rest, ends with sending text to festival --tts. Festival is the engine that does all the heavy lifting of converting text to speech.
#!/bin/bash ## ## Say the forecast outloud ## json=`curl -s 'http://api.openweathermap.org/data/2.5/weather?q=Arlington,VA,USA&units=imperial'` description=`echo $json | jq '.weather[0].description'` temp=`echo $json | jq .main.temp | sed 's/[.].*//'` echo "It is currently $temp degrees, with $description in Arlington" | festival --tts
sayheadlines - This guy reads off the top 10 latest headlines from CNN. This could be adapted to read off any RSS feed, I suppose. I've been less than impressed at the top stories CNN has mentioned, so perhaps I need to find a better new source. Still, I'll often go the whole day without checking the news. Often some horrific (or very occasionally terrific) event will happen and I'll be totally clueless until Shira arrives home. Not any more.
#!/bin/bash ## ## Announce CNN headlines ## URL=http://rss.cnn.com/rss/cnn_us.rss buffer=/tmp/headlines.$$ echo "CNN top headlines." >> $buffer index=1 curl -s $URL | grep '<title>' |grep -v 'CNN.com' | head | while read headline ; do clean=`echo $headline | sed -e 's|<title>||' -e 's|</title>||'` echo "$index: $clean." >> $buffer index=`echo $index + 1 | bc` done cat $buffer | festival --tts rm $buffer
sayinbox - My first thought was that writing a script to read off my Gmail was going to require some sort of network hacking. Not so. All you need is this one trick: Google provides an RSS feed for your inbox. Now when a million dollar offer comes in from a Nigerian Prince comes in, I can get right on it.
#!/bin/bash ## ## Inspired by: ## http://www.commandlinefu.com/commands/view/3386/check-your-unread-gmail-from-the-command-line ## USER='XXXXXXXXX' PASS='XXXXXXXXX' buffer=/tmp/email.$$ (echo '<?xml version="1.0" encoding="UTF-8"?>' ; curl -u "$USER:$PASS" -s "https://mail.google.com/mail/feed/atom" | sed -e 's|<?xml version="1.0" encoding="UTF-8"?>||') | xmllint -format - > $buffer num_messages=`grep '<entry>' $buffer | wc -l` first_subject=`grep '<title>' $buffer | head -2 | tail -1 | sed -e 's|<title>||' -e 's|</title>||'` if [ $num_messages -eq 0 ] ; then message="You have no new messages" else message="There are $num_messages new messages in your inbox. The subject of the most recent message is: $first_subject" fi echo $message | festival --tts rm -f $buffer
Of course, this is just the tip of the iceberg. Over time, I assume I'll add more scripts as I think of additional bits of info I want quick access to. Linux certainly makes this easy enough to do.
Any suggestions for what I should script next?
Thursday, February 05, 2015
Sounds awful, but it's all mine - Adventures in programmatic music generation
A few years ago I discovered the RTTTL "music format." RTTTL, as a quick refresher, was a language used to exchange ring tones. Essentially it's a text file that lists out which notes to play; couldn't be simpler. The textual nature of it means that it's trivial to generate songs using any programming language.
Yesterday evening I had about 3 hours on the train to kill. I had planned to work on my Laptop but the "free" WiFi was apparently maxed out by passengers who had the same idea. So I put the laptop away, busted out the Bluetooth Keyboard and started to brainstorm as to how I could use Scheme to generate sweet, sweet music.
I can tell you that I fully missed the mark on the "sweet, sweet" part. But I did manage to use Scheme to generate RTTTL files, which in turn played on my mobile device. I ended up implementing a series of functions to generate Morse Code from arbitrary text, as well as a function to generate a random song. Here, give a listen to some samples:
The Morse Code needs tuning to be truly accurate, and the "random songs" my program generates are more like random noise. But this is hardly a loss. I've always been curious what an algorithm might be that would generate at least a passable tune, and now I've got the toolkit to experiment with this.
I've included my source code below, and you can grab it from github here.
One feature of RTTTL is that your phone, for historic reasons, probably plays it without needing a special application. The flip side of this, though, is that finding a way to play the RTTTL files outside of your phone is non-trivial. I looked all over Google Play for an app that would convert my RTTTL file to a .wav or .mp3 file, but had no luck. Your typical music player and music publication site (I'm looking at you, soundcloud) is going to be clueless as to what an RTTTL file is. Luckily, I was able to find a recipe that works for converting from RTTTL to .wav. Here it is:
- Install the SMS-Ringtone-RTTTL-MIDI perl module
- Grab this script I prepared to leverages this module to create a MIDI file
- Install some MIDI related tools, including timidity++ and fluidsynth
Once the above steps are taken care of, you can convert foo.rtttl to foo.wav by using the following sequence:
rtttltomidi < foo.rttl > foo.midi timidity foo.midi # listen to your creation fluidsynth -F foo.wav /usr/share/sounds/sf2/default.sf2 foo.mid
That last step was inspired by this post, which also goes on to explain how to convert the .wav to .mp3.
Getting these libraries together sounds tricky, but on my new Linux box the whole process was surprisingly painless.
OK, so now it's your turn. Go off and write some code which in turn writes some amazing music!
;; ;; RTTTL - let's generate some sounds. Below we generate both ;; Morse Code and a random "song" ;; (define (&& . any) (apply string-append (map (lambda (x) (cond ((number? x) (number->string x)) ((symbol? x) (symbol->string x)) (else x))) any))) (define (implode sep parts) (let loop ((parts parts) (accum '())) (cond ((null? parts) (apply && accum)) (else (loop (cdr parts) (if (null? accum) (list (car parts)) (append accum (list sep (car parts))))))))) (define (string-head text) (substring text 0 1)) (define (string-tail text) (substring text 1 (string-length text))) (define (explode sep text) (let loop ((text text) (current "") (accum '())) (cond ((equal? text "") (if (equal? current "") (reverse accum) (reverse (cons current accum)))) ((equal? (string-head text) sep) (loop (string-tail text) "" (cons current accum))) (else (loop (string-tail text) (string-append current (string-head text)) accum))))) (define (rtttl title notes) (string-append title ":d=4,o=5,b=160:" notes "\n")) (define (save filename contents) (call-with-output-file (string-append "/sdcard/Documents/" filename) (lambda (out) (display contents out)))) (define morse-map '((a ".-") (b "-...") (c "-.-.") (d "-..") (e ".") (f "..-.") (g "--.") (h "....") (i "..") (j ".---") (k ".-.-") (l ".-..") (m "--") (n "-.") (o "---") (p ".-.-") (q "--*-") (r ".-.") (s "...") (t "-") (u "..-") (v "...-") (w ".-") (x "-..-") (y "-.--") (z "--.."))) (define (morse-char c) (let ((needle (string->symbol (string (char-downcase c))))) (cond ((eq? c #\space) " ") ((assoc needle morse-map) => cadr) (else (morse-char #\x))))) (define (morse-word text) (let ((chars (map morse-char (string->list text)))) (implode "|" chars))) (define (morse-string text) (let ((words (explode " " text))) (implode "_" (map morse-word words)))) (define (morse-notes encoded) (let loop ((chars (string->list encoded)) (accum '())) (cond ((null? chars) (implode "," (reverse accum))) (else (loop (cdr chars) (cons (case (car chars) ((#\.) "c5") ((#\-) "a7") ((#\|) "p") ((#\_) "p,p,p")) accum)))))) (define (morse-rtttl message) (rtttl message (morse-notes (morse-string message)))) (define (string-reverse text) (apply string (reverse (string->list text)))) (define (range low high) (if (> low high) '() (cons low (range (+ 1 low) high)))) (define (random-elt items) (list-ref items (random-integer (length items)))) (define (random-between low high) (+ low (random-integer (- high low)))) (define (random-note) (let ((note (random-elt '(c c c c d d e f g a p))) (len (random-elt '(1 2 4 8 16))) (oct (random-elt '(5 6)))) (if (eq? note 'p) (&& len note) (&& len note oct)))) (define (random-name) (&& (random-note) (random-note) (random-note) (random-note))) (define (random-notes) (implode "," (map (lambda (i) (random-note)) (range 0 (random-integer 100))))) (define (make-buffer) (let ((buffer '())) (lambda (x) (if (equal? x 'get) (implode "," (reverse buffer)) (set! buffer (cons x buffer)))))) (define (random-song) (let ((chorus (random-notes)) (buffer (make-buffer))) (for-each (lambda (i) (buffer (random-notes)) (buffer chorus)) (range 1 (random-between 5 10))) (buffer (random-notes)) (rtttl "Music By Scheme" (buffer 'get)))) (save "hw.rtttl" (morse-rtttl "Hello World")) (save "sos.rtttl" (morse-rtttl "SOS")) (save "random.rtttl" (random-song))
Wednesday, February 04, 2015
The Agony and Ecstasy of Linux on the Desktop
It all started with an anonymous comment: why run Windows if all the software you're running is Linux flavored? I didn't have a particularly good answer.
Thing is, there used to be a time when I only ran Linux. Shira reminds me that back in the day I wouldn't even *allow* a Windows box under my roof (really? She says so). So how did I drift so far away from my roots?
Who cares. Let's fix this.
In theory, you can experiment with Linux before you install it as your primary OS. By default, Fedora comes with a (amazingly cool) Live version that lets you run Linux without messing with Windows. And then there's always the option to install Linux on old laptop or desktop, and play with it there. I've talked about doing those things for years.
However, the only way I know how to truly use Linux is to frigging Use Linux. That is, jump in the deep end of the pool and hope you can learn to swim in time. And so that's exactly what I did: I installed the latest version of Fedora on my brand new Dell laptop. Rolling back to Windows is possible, but it ain't easy.
My adventure started at 6:56am on January 26, 2015. I know this, because for the last 10 days I've been keeping a log of my trials and tribulations.
I'll spare you the details of reading the log (though, please, be my guest) and give you the Cliff Notes version. First, I was amazed at how easily Linux installed. It was effortless. And then I was amazed at the slick desktop that booted up. And then reality started setting in. First, stuff just didn't work. For example, I couldn't move windows around by clicking and dragging the mouse. I was left baffled: was something not configured properly, or was I just clueless as to how a modern version of Linux works?
And then I got more frustrated: what I was looking at was a slick user interface, which as far as I can tell, was trying to be a better version of Windows. But if I wanted Windows, I would have just continued to use the version of Windows I already had!
So, I turned off Gnome, that fancy Desktop Manager and turned on plain o'l X-Windows. Hello 1999! I was no closer to a functioning system, but at least I had that warm fuzzy feeling that comes from recreating the Good Old Days.
Next up, I installed ratpoison. This infamous Window Manager let's you manage your desktop by barely touching the mouse. It's crude and cryptic. Now this is a Linux box!
But still, the list of things that didn't work far outnumbered the list of things that did. Firefox menus didn't work. Selecting text didn't work. Copying between an X-term and Firefox didn't work. WiFi didn't work. Good Lord, what did work?
At this point, I wanted off this ride. My Windows + Cygwin + Emacs approach may have been klugy at times, but at least the friggin mouse worked!
Then I finally had a small bright spot. While working on a network hardware post, I realized that I could measure the strength of my wireless router by kicking off nmcli like so:
while sleep 1; do nmcli dev wifi >> wifi.stats ; done
I could then walk around the house and collect data. When I was done, I used awk to pull out the WiFi signal strength and generated this graph. Could I have done this in Windows? Sure, but not with zero effort.
As I thought about this victory, I remembered that Windows has always been the OS where things Just Work, whereas Linux let's you be productive in ways you never even imagined. Sure, that's changed over the years: X used to require massive amounts to effort to configure (and you risked ruining your monitor!), now it Just Works. But as I was learning first hand, when it comes to a flawless system out of the box Windows still has Linux beat. But, as my little network script showed, Linux can run circles around a typical Windows configuration when it comes to actually getting things done.
So I pushed forward. And so far, I'm glad I did.
Over the last few days, I've figured out many an issue. My trackpad, for example, needed to be marked as a 'clickpad' to truly function (ahhh, now it works!). I got copy and paste working correctly, my phone to be detected, multiple monitors to work well with ratpoison and a slew of other victories.
Things are far from fully setup (I'm looking at you, 'SD Card'), but they're getting there.
More importantly, I've gotten a number of scripts in place to streamline tasks that used to be all manual. I can now type:
s5 mount ; s5 grab ; s5 unmount
to grab all the images from my cell phone and have them archived in directory named with the date. Here's the script:
#!/bin/bash ## ## A script for working with our galaxy s5 ## S5_ROOT=$HOME/devices/s5 S5_PICTURES=$HOME/Pictures/s5 action=$1 ; shift case $action in mount) simple-mtpfs $S5_ROOT ;; umount) fusermount -u $S5_ROOT ;; slurp) for img in $S5_ROOT/Card/DCIM/Camera/*.jpg ; do date=`imgcreated $img` echo "$img -> $date" mkdir -p $S5_PICTURES/$date mv -i $img $S5_PICTURES/$date done ;; *) echo "Usage: `basename $0` {mount|umount|slurp}" exit ;; esac
It's this sort of automation that makes me feel like this Linux experiment is more than just a walk down memory lane.
Expect to see more scripts and tales from the command line as I continue to refine my desktop Linux experience.
Tuesday, February 03, 2015
They Had Me At Knicks Game
Jumped on the Acela today and headed to NY to meet one of my clients. Man, that's an easy trip. Anyway, tonight the plan was to take me out to dinner. That plan changed.
Instead they took me to a Knicks game.
And we 'sat' in a luxury suite.
And they brought in Kosher food.
That's not just a bathroom. That's our suite's private bathroom, thank you very much.
If they wanted to impress me, mission accomplished.
Monday, February 02, 2015
Knights, Horses and Dinner - A Medieval Adventure
This past weekend I took in my very first Medieval Times show, and I walked away pretty dang impressed. As you cross the threshold from the Arudel Mills Mall into the Medieval Times 'Castle' you're supposed to be transported back in time to the days of knights and ladies. Alas, you're actually just led to an extremely elaborate set of gift shops. But, I'll forgive them for that.
Once seated in the arena the real fun begins. Each section of seating corresponds to a particular knight (Go Blue Knight!) and we proudly rooted for ours. For $2.00 or $5.00 we could have bought a flag to wave; we passed on this little extravagance. Dinner is then served as the knights perform a tournament of skill. The feats performed, snatching up rings and racing through the arena were actually pretty dang impressive. They certainly made it look easy, though doubt it is.
The show culminates in a jousting competition and various sword fights. As choreography goes, it wasn't bad. There's lots of clanking and crashing as various arms strike shields and each other; but of course, nobody ever gets hurt.
Then there's the food. I was actually surprised at how well the food worked out. The standard meal consisted of bread, soup, chicken, a rib and desert--all served without silverware. The kids enjoyed that! They also had a vegetarian option which was essentially a rice and bean stew. I've got to say, the veggie meal was quite tasty.
Other than the fact that I felt nickeled and dimed throughout the show ($2.00 for this, $2.00 for that), it really was very entertaining. The horses are gorgeous, the feats performed by the knights impressive, and the up-close seating meant I could try my hand at capturing some interesting photographs. The kids certainly enjoyed the whole shebang, which of course, is key.
If you're looking for an extravagant, indoor activity (perfect on a winter day) that should please the whole family, Medieval Times is worth your time.