iPhone VPN (Virtual Private Network)

vpn.jpg
It's become quite clear that using public Wi-Fi is getting increasingly dangerous. New attacks are constantly being created. The best solution for this is to use a Virtual Private Network, VPN, which eliminates most of the risks. Using a VPN prevents sniffing unencrypted connections, DNS redirection, tracking cookie insertion, and more exotic attacks like the export-grade SSL MITM attack.

The VPN support built into iOS is quite good. When configured using keys and certificates stored in the keychain it's quite secure. And it automatically activates, reconnecting when you wake from sleep or switch between Wi-Fi and 3G/4G/LTE. It only takes a second or two to reconnect, and I don't notice a speed difference in use.

By far the easiest way to handle the server side is to just sign up for a VPN service. Quick and easy, though you generally have to pay a monthly fee.

This post is for the tiny sliver of the population that is security paranoid, has server(s) at their disposal, and is averse to spending money. This solution is free, if you have a server to install it on. You can theoretically install the server at your home, though if you have an asymmetric Internet connection your phone browsing will be limited to less than your home upload speed. And you'll probably have to deal with dynamic IP address issues. This post mainly focuses on installing on an Internet connected server, in my case a fractional shared Linux virtual private server, which has more than enough power for this use.

While the underlying VPN implementation is part of iOS, I couldn't figure out how to configure it to use OpenVPN directly. Fortunately, it's easy using the free OpenVPN Connect app. Just install that from the App Store and the rest is pretty easy. 

The main thing is to follow these instructions to set up the server side OpenVPN under Linux. The instructions are good.

There are more instructions here, but the ones above should be sufficient.

Follow the instructions in the first link to create a client certificate. I called mine iphone6. Then issue this command in /etc/openvpn/easy-rsa/keys. Replace "iphone6" with whatever you called your client certificate. And repeat this for as many clients as you want to connect. Always create separate key/certificate pairs for each device!

openssl pkcs12 -export -in iphone6.crt -inkey iphone6.key -certfile ca.crt -name iphone6 -out iphone6.p12

Assign a strong export password for safety. You'll be asked for it again when you import into the keychain, but not every time you use the VPN, so use a long and strong password because you only need to type it 3 times, ever.

You'll need to email this to yourself, so you can open it on your phone. When you click on the attachment, it will prompt you to install the key and certificate, which you should do. This is why you need to protect it with a strong password!

You may be able to use iTunes Sync to transfer this file. I was unable to transfer it using DropBox. Well, I was able to transfer it, but I couldn't get it open and import automatically so that doesn't help.


Here are some FAQs for the OpenVPN Connect iOS app:

The other thing you need to do is create the .ovpn configuration file. Since I imported the key and certificate right into the iOS keychain using a .p12 file, above, I don't need to include them in the ovpn file. Sending around unencrypted keys in the ovpn file isn't very secure!

Here is the file I created, iphone6.ovpn:

client
dev tun
proto udp
remote yourserver.com 1194
nobind
persist-key
persist-tun
ns-cert-type server
<ca>
-----BEGIN CERTIFICATE-----
MIIDgTCCAuqgAwIBAgIJAMZnAIUXhQijMA0GCSqGSIb3DQEBBQUAMIGIMQswCQYD
**** replace this block with your ca.crt! ****
oPgy58A4BOx6yrr0cI2UErnmsFPLehbo9D5YJVkx7PN97wcunQ==
-----END CERTIFICATE-----
</ca>
Don't just copy and paste this! You need to put your ca.crt from /etc/openvpn/ca.crt, or you won't be able to connect. Also, change the remote to your server's hostname or IP address. Even though I added the CA to the .p12 file I wasn't able to connect unless I put this in the .ovpn file. Also, this is low-risk because the ca.crt is intended to be public knowledge to validate the certificates. 

Just email this file to yourself and open it on the iPhone. It should ask if you want to open it with OpenVPN Connect and you should. And that's it! You can do this step with Dropbox, if you prefer.

You should be able to connect now.

If it fails, check the server logs in /var/log/syslog. 

Also, once you've successfully connected you can toggle the VPN on and off using Settings - VPN, just a click away. (For some reason you can't always turn it back on there, but Settings - General - VPN or the OpenVPN Connect app seem to always work.)

If you use a public Wi-Fi hotspot that requires that you agree to terms and conditions on a web page first, you'll probably have to turn off the VPN, open a web browser to any page, agree, then you should be able to turn the VPN back on. It's a small price to pay for security!

There is no way to have it automatically deactivate the VPN when, for example, you're at home. While that sounds enticing, if that sort of thing was even possible it probably opens up an opportunity for a targeted man-in-the-middle attack where a public Wi-Fi hotspot could pretend to be your home Wi-Fi. Anyway, with no noticeable performance penalty, I'm not worried about the overhead.

I've been using it for several days now and it works great!

Using a Chromebox as a Linux server

chromebox.jpg
The Asus M004U Chromebox is reasonably priced at $ 167-ish. It only has 2 GB RAM and 16 GB SSD, but for a small Linux server that's plenty. And it's a tiny little thing. I think it's fanless, or at least it's silent. It has an Intel Celeron i86_64 processor. Also, it has an SD card slot, or you can plug a USB-3 hard drive into it, so storage isn't really an issue. And both Ethernet and Wi-Fi. I think the Ethernet is gigabit, even.

The first time I used it I just booted it up normally. It's fast and seems to work well. If all you need to do is web browsing, email, watch YouTube videos, maybe some stuff in Google Docs, it really does seem like this is all you'd need. Or a Chromebook if you wanted a laptop form-factor instead. But anyway, I wanted a Linux server.

Chrome OS appears to be a variant of Linux, but I wouldn't call the Chrome OS a Linux installation, mainly because you can't modify it. It's completely locked down, and even in developer mode there are limitations of what you can modify.

There are three ways of running actual Linux:

1. chroot Linux under Chrome OS - very little danger of breaking anything, but you're basically running Linux under Chrome OS.
2. Dual boot, so you can boot into either.
3. Replace Chrome OS with Linux.

Since I was unlikely to use the device as a Chrome OS device again, and you can recover it easily if you need to by downloading a restore image onto an SD card or USB flash drive, I went with completely replacement (#3) so I could reclaim all of the space in the SSD used by Chrome OS.

I was going to do Linux installation from an SD card, and I downloaded the Ubuntu LTS server iso file, but I was doing it from a Mac. Here are instructions for making a bootable SD card or USB stick on the Mac from an iso file:


The short version:
hdiutil convert -format UDRW -o ubuntu.img ubuntu-14.04.1-server-amd64.iso
diskutil list
diskutil unmountDisk /dev/disk6 # replace with the disk number of the volume you want to write to
sudo dd if=ubuntu.img.dmg of=/dev/rdisk6 bs=1m

I copied to a class 10 SDHC card, and it only took 52 seconds to copy the image. Note that it won't be readable by OS X, so if prompted about that, just Ignore or Eject.

This post had everything I needed to get started. It was for Fedora and I'm installing Ubuntu, but the process is pretty much the same.


I'll briefly repeat the steps from that post here, but you should go there and read the details.

The reset button is the little circle above the Kensington lock port, next to the SD card slot, on the left side. Insert a paperclip into the hole to gently press the button.

With the unit powered down, press and hold the reset button while powering on to boot into recovery mode. This will erase any data that you have on the box!

Hit Ctrl-D to turn off OS verification.

Hit the reset button again to enable developer mode. Let it sit for a while, I think it will reboot.

I was unable to enter a shell by using a Mac keyboard and neither Ctrl-Alt-F2 or Ctrl-Alt-Right Arrow worked. (I used Option instead of Alt, since it was a Mac keyboard. That usually works.)

However from the Chrome desktop I was able to enter the "crosh" shell by hitting Ctrl-Alt-T. From there, use the "shell" command to get a normal bash shell.

The booting into legacy OS command in the page above was wrong for me. It's chromeos-firmwareupdate not upgrade.

sudo -s
/usr/sbin/chromeos-firmwareupdate --mode=todev
crossystem dev_boot_usb=1
crossystem dev_boot_legacy=1

Install the patched SeaBIOS binary with USB-3 support:

cd /tmp
wget http://johnlewis.ie/asus-chromebox-SeaBIOS-new.bin md5sum asus-chromebox-SeaBIOS-new.bin
flashrom -w -i RW_LEGACY:asus-chromebox-SeaBIOS-new.bin

Note that doing a full Chrome OS restore appears to also overwrite the legacy boot slot, so even if you completely screw up this step you can still recover from it.

The next step is optional, but if you're going to make a dedicated Linux box I highly recommend it. It will require opening up the box. One page I read said this doesn't void the warranty unless you break something, but don't quote me on that. If you decide not to do this, scroll down and continue below.

The problem is that once you're in developer mode, you get the "white screen" during boot that warns you that OS protection is off, if you let it sit for a while (or hit Ctrl-D) it proceeds to boot Chrome OS. In order to run Linux, we use the legacy mode, which requires that you hit Ctrl-L during the white screen. If you remotely reboot Linux, it will then be stuck at the white screen forever. These steps make legacy boot the default, so after the white screen it defaults to booting Linux. 

Also, make sure you do these steps before you install Linux, especially if you're doing an overwrite installation as described here. Once you erase Chrome OS there's no way to reset the default boot OS unless you reinstall Chrome OS, then install Linux again! (I did that.)

The first thing you must do is remove the write-protect screw. Remove the four rubber pads on the bottom the the device. Under the rubber pads are 4 phillips screws. Remove those. Pry the case apart. The split is right at the base, so if you stick a screwdriver in the crack you can probably get it apart without too much force.

With the device sitting board-side-up and the ports away from you, the write-protect screw is in the back right. It's near the ports, on the side that isn't the side with the RAM. It's a silver screw with an unusually large head (pointed to by the pink arrow, below). Remove the screw. That's it for the hardware modifications. You can put the device back together now.

wpscrew.jpg
Incidentally, the 2 GB RAM Chromebox I got had only one of the memory slots filled. It would probably be inexpensive to add a second 2 GB and double the RAM, though I didn't do that.

Boot back into Chrome OS and bring up a shell. I used Ctrl-Alt-T then use the shell command.

sudo -s
flashrom --wp-disable
sh /usr/share/vboot/bin/set_gbb_flags.sh 0x488

I used 0x488 instead of 0x489 in the instructions above, so it will pause longer on the boot screen, which makes it easier to intercept it. It makes reboots take longer, but it's not that much longer.

Under no circumstances should you issue the set_gbb_flags.sh without removing the screw and issuing the flashrom --wp-disable command! If the flash is write-protected, only part of it is actually write-protected. The set_gbb_command will overwrite the part it can, resulting in a corrupted image and a need to restore Chrome OS. Don't do that! (I did.)

More info on the resetting the default boot:


Continue here if you didn't remove the write-protect screw and change the default boot OS.

Insert the SD card or USB stick with the bootable Linux distribution iso on it.

Reboot the Chromebox.

At the white "OS validation is off" screen hit Ctrl-L to enter SeaBIOS, or wait if you've changed the default.

Hit ESC once to bring up the boot menu. You have to do this pretty quickly, basically as soon as the screen turns black. Boot off the Linux installation media. It should be the second option in the boot menu.

In the original post above, there was a need to add a mem=1024M option, but that was not necessary for Ubuntu 14.04 server.

Follow the normal Ubuntu server installation. When it asks you if you want to unmount /dev/sda, make sure you answer yes. That's the Chrome OS disk, and I wanted to completely overwrite it, so the volumes must be unmounted.

From Ubuntu server installation, I just installed OpenSSH, DNS, LAMP. Even though there is a Tomcat/Java option, I wanted to install Oracle Java and my desired Tomcat version so it would match my other servers. And of course once it's running you can apt-get install anything you want.

That's it! I now have a fully functional tiny Linux box!

Waveform drawing



I wanted to draw some waveforms for my NEC IR decoder for SparkCore post. I suppose I could have drawn them by hand, or maybe researched some hopefully free software that would make drawing them easier. I did not do that, however.

I instead wrote a small Javascript library that creates SVG waveform diagrams in a web browser. It should not be considered the end-all of waveform drawing libraries, as I only implemented enough to draw the diagrams I wanted to draw. It could be a starting point for a full-featured library. Or an example of how to create SVG drawings from Javascript.

SVG is the scalable vector graphics image format. Unlike formats like JPEG or PNG, it specifies lines, arcs, and text mathematically rather than by pixel, so you can zoom in. And when you print, the text is always clear and printed at the full resolution of your printer.

Important Caveat: SVG does not display in Internet Explorer 8 and earlier! It works in pretty much everything else, however. This site has a good list of browser support for SVG.

My library relies on three Javascript libraries:

jquery
Keith Wood's jquery.svg for creating SVG images
FileSaver.js for locally saving the SVG images

You can view the sample site here.

Here's the Javascript for how the waveforms are described.



And, finally, the library. It's pretty straightforward, I think.



NEC IR Remote Decoder for SparkCore

proto_remote.jpg
I wanted to be able to read the IR signals from the 9-button SparkFun remote control ($ 4.95) from the SparkCore. Turns out, it was quite easy, a tiny bit of hardware and some software.

The first thing is the IR receiver. It's a SparkFun 8554 ($ 9.95), which contains a Vishay TSOP853 IR receiver. It outputs a nice digital stream of bits after demodulating the IR data stream. It connects to Vcc and ground, and works at 3.3V or 5V, but I used it at 3.3V that the SparkCore uses. The final pin is data, which I connected to D0. The TSOP853 has an internal pull-up, so you don't need an external one.

The hardware is capable of receiving other 38 kHz IR codes including RC5/RC6, RCMM, r-step and XMP, basically everything except Sony, which is 40 kHz. The software I wrote only decodes NEC, but of course you could modify it to decode whatever you wanted. Judging from a quick sampling of remotes on my desk, the TiVo and Apple remotes also use the NEC protocol. My Scientific Atlantic cable TV remote and TCL TV remote do not.

There is a good explanation of the NEC IR protocol here. Note that the output from the TSOP853 is inverted from the logic levels described on that page.

A normal transmission begins with a header, a logic low for 9000µs and then a high for 4500µs. After this there's a space, which is 562.5µs and then a logic bit. This is 562.5µs for logic 0 and 1687.5µs for logic 1. Note that these times are very approximate, and remote controls may vary quite a bit from the published specification.

Essentially, each bit consists of a space and then a high pulse of varying width depending on whether it's a 0 or 1. There are 32 bits in the code, sent MSB first, and then another space to mark the end of the transmission.


(If you don't see a waveform diagram above, upgrade your browser! IE9 or later, or pretty much any version of Chrome, Firefox, Safari or Opera should work fine.)

As I mentioned the MSB is first, so here's how the value 0xE5, a binary value of 11100101, is sent:



A real transmission would be 32 bits, not 8 bits, and would include the header above, but you get the idea. Also, there's actually only 16 bits of information in the 32 bits of data, because 16 bits of it are complement values for error checking. But typically the values are listed with all 32 bits (4 bytes), as they are below.

The only other thing of note is the repeat sequence, which is 9000µs of low following by 2250µs of high instead of the normal header. This is used when you hold down a button - the original key is sent then the repeat sequence is sent repeatedly while the button is being held down.


There are two reasonable ways to decode the IR signals. You can do edge-triggered interrupts or you can use a timer and poll at some multiple of 562.5µs and count the number of samples at a given logic level. I used the interrupt method, as it's easy to do on the SparkCore and is quite efficient. Here's the code for the SparkCore:



I think the code should be self-explanatory.

By the way, I think this should also work on the Arduino, except for the Spark.publish line. You'll have to do whatever you want to do with the IR code in place of that line.

In my case, the Spark.publish line sends the event into the cloud. It's received by an Apache Tomcat webapp running a Server-Side Events (SSE) client, which then parses the data field for the hex button code. For the 9-button SparkFun remote, the codes are:

    public static final int REMOTE_POWER = 0x10EFD827;
    public static final int REMOTE_A = 0x10EFF807;
    public static final int REMOTE_B = 0x10EF7887;
    public static final int REMOTE_C = 0x10EF58A7;
    public static final int REMOTE_UP = 0x10EFA05F;
    public static final int REMOTE_DOWN = 0x10EF00FF;
    public static final int REMOTE_LEFT = 0x10EF10EF;
    public static final int REMOTE_RIGHT = 0x10EF807F;
    public static final int REMOTE_SELECT = 0x10EF20DF;
    public static final int REMOTE_REPEAT = -1;

And that's it!


Oil fill sensor

installed.jpg
I wanted a way to automatically detect how much fuel oil I have. There are some off-the-shelf remote sensors of varying cost, but I wanted something that could interface with my computerized environmental monitoring and notification system and wasn't too expensive.

There are two common types of sensors, a magneto-resistive sensor with a float and an ultrasonic sensor. Ultrasonic is neat, but the ones designed for use in fuel tanks are quite expensive, so I went with the less expensive float sensor.

I got one from Syba Systems for $ 129. I got the 0 - 5 VDC output model, 42" long, for a vertical 275 gallon tank. It's accurate to about 1/2" which isn't great, but is acceptable for my purposes. It fits in a 1 1/2" NPT hole, but my tank had a 2", so there's a reducing adapter as well. There was previously a 2" plug in that hole. That does raise the sensor up a little so there's probably a little bit more oil than the sensor would indicate, but that's not really a problem.

This is what it looked like before installation. The black thing on the left is the float, which slides up and down the stainless steel rod.

sensor.jpg
It requires a power supply of 12-24 VDC. Normally you'd need a 12 VDC adapter, but I already had one with sufficient capacity that powers the existing proximity sensor and electronic lock at my front door.

The 0 - 5 VDC sensor analog output I decided to attach it to my existing Phidgets 8/8/8 USB-based analog and digital I/O board. That board has 5 V analog inputs and is already integrated with my server infrastructure, so that was a logical choice.

If I were starting out from scratch, I'd probably use a SparkCore instead of Phidgets. It's less expensive, and has built-in Wi-Fi. There's one caveat to this - it has 3.3V analog inputs, but that's not that big of a deal, as I'll explain. 


I'm glad I tested the sensor, because it seems that it can output more than 5 VDC - I got 6.19 volts!

highvoltage.jpg
To be safe I put a resistor voltage divider made with 10K resistors in front of the ADC (analog to digital converter) input. This will result in a maximum of around 3V, but since the Phidgets ADC is 10-bits, there's still plenty of resolution. This is also the solution to using a SparkCore since that will be sure to keep the input below 3.3V.

divider.jpgThis is the Phidgets installation.

phidgets.jpg
The Phidgets ADC outputs a value of 0-1000, I think. With my voltage divider I seem to get something around 0-619, which seems about right.

The other issue is when translating the gallons, the curvature of the tank. This is just a simple math problem, the area of a circular segment given its height. I didn't remember the formula, but it only took a few seconds of searching to find it.


I already have an infrastructure where my Apache Tomcat webapps can send Apple Push Notifications to a custom app (called "Lock") on my iPhone. Here it is letting me know I'm low on oil:

phone_tank_low.jpg
And that the tank is currently being filled:

phone_tank_filling.jpg
One of the problems I'm having is that the data from the sensor is not particularly stable. I'm not sure whether it's the sensor or the ADC, but I'm tracking it to see how best to smooth the data. Well, samples 11-19 are during filling, so it's expected that those would be varying, but the other ones are more concerning. Maybe I should have gone with ultrasonic!

levels2.jpg
Until I figure out how to smooth the data and get a larger sample of data, I'll wait before implementing the next part, an analysis of how much oil I'm consuming. I have accurate outdoor temperature values by the hour from my weather station, so I should be able to accurately calculate heating degree days for comparison, as well.


Spotify Next Track Button

done.jpg
Wouldn't it be handy to have a big button you could hit that would activate the Spotify Next Track button? It wasn't that hard to do! I had the prototype working in couple hours.

The reason for this is that I often listen to other people's public Spotify playlists, and there are always a few songs I don't really like and would rather skip over. Hitting the next track button when I'm sitting at my Mac is no problem, but I use Airfoil and Apple Airport Expresses to pipe music all over my house. It's less convenient from my kitchen.

I could open up my iPhone and use the next track button in Remoteless, but that takes several seconds. A big button would be more fun.

Before you get too excited about this project, I should warn you that not all of the code is included. There is a piece that's not published. I'll tell you how it works, but you'll need to implement that yourself. Sorry about that.

The hardware couldn't be simpler. This is my prototype, which has a little button, but it works the same as the big button. It uses a SparkCore from spark.io. It's a tiny computer designed to work with the cloud. It's mostly software compatible with the Arduino but with built-in Wi-Fi and cloud computing support. It's powered by Micro USB and can be battery powered. And it's only $ 39!

The button is connected to the D0 input, and the inputs can be configured with internal pull-ups, so the switch only needs to be connected to ground. Easy!

proto.jpg
I got the button from SparkFun for $ 9.95. It comes in several colors, and there's a less expensive version for about half the price. It's big - almost 4" (100 mm). The button has an light in it, but I did not use the light in this project - I only used the switch.

I also got the mini-modular protoboard from SparkFun for $ 3.95. It's not strictly necessary, but it's a convenient size to fit inside the project box. That's not the one above, that one came with the SparkCore, but I wanted a smaller one inside the box. A soldered protoboard would also be a reasonable choice.

Finally, I got the 2A (5V) micro-usb power supply from SparkFun for $ 5.95. Any micro-USB power supply will work. And the SparkCore uses a miniscule amount of energy - less than 1W at 120V at the wall plug with this power supply.

The only other thing I purchased was a box from Digi-Key, $ 8.40. It's a 4.6" x 4.6" x 2.4" Black ABS plastic utility box. It was just the right size for the button.

You set up the SparkCore first using the smartphone app, associating it with your account and telling it your Wi-Fi password.

Programming it is done using Wiring, the same C-like language as the Arduino, but you can program it right from your web browser and flash the Spark over Wi-Fi. It's pretty cool. Here's my code:

Not much to it! The main thing is that when you press the button an event is sent into the cloud.

One of the nice things about the Spark system is that even though my code is essentially listening for events, my code makes requests outbound using HTTP Server Side Events (SSE). This eliminates having to open ports in the firewall, deal with dynamic IP addresses, etc.. Easy and pretty efficient.

My code is written in Java and runs in Apache Tomcat. I'm not releasing the source because it contains a bunch of proprietary code that I don't want to release. That infrastructure is why I was able to write it so quickly, however. But I will explain how it works, and you could easily do something similar in your favored language.

The Java code basically runs a SSE client that makes a connection to the Spark API server and waits for messages. If the connection is lost, it just makes a new connection. One of the messages is an event message, which signals that the button was pressed. I then call an AppleScript to trigger Spotify to advance to the next track.

And finally, the AppleScript. This tells Spotify to advance to the next track. I'm not sure if Spotify for Windows has similar capabilities, but this is super quick and easy on the Mac. Well, if you have Java code that can use osascript on the Mac to interface with AppleScript. That's left as an exercise for the reader.

As for the box, mark the location of the holes:

marked.jpg
I started with smaller drills are worked up to larger sizes to reduce the risk of shattering the box, though ABS is pretty forgiving. The side hole is for the power supply and is 1/2" (the USB connector end needs to fit through it). The top hole is 1" to hold the switch, though I only had a 7/8" twist drill so I used that and just widened it a bit with rocking the drill bit and a utility knife.

drilled.jpgThis is what the button looks like:

button.jpgbutton_bottom.jpg
On the bottom is the microswitch and lamp (not used). A little twist allows that to be removed, at which point you can unscrew the white nut.

Here are the leads soldered to the switch after I mounted the switch the top of the box.

soldered.jpg
And here's the insides:

box_inside.jpg
And that's it. The final product is show at the top of the page.


iOS WebView error and debug logging

Theoretically one of the advantages of implementing a user interface using an embedded UIWebView is that you can debug the whole thing outside of the iOS app, in a regular browser with full debugging facilities. This is true, but sometimes you want to see what's going on in the Javascript code as it's running on the iOS device. Unfortunately, the output from console.log and Javascript error reporting seems to just disappear, rather than go into the debug console in XCode.

Fortunately, this is remarkably easy to fix. It's just a little code on the Objective-C side, and a little on the Javascript side. Note that this is intended for debugging use only. It's a fair amount of unnecessary overhead otherwise.

Note that this uses a custom URL protocol handler. It's small, fast, and a really good way to have Javascript call into Objective-C, asynchronously. While it's easy for Objective-C to call into Javascript using stringByEvaluatingJavaScriptFromString, there is no facility built-in for going the opposite direction.

A few people have played tricks using the location.href, or the href of an invisible iframe or NativeBridge, but I don't really like those solutions. My solution allows you to pass essentially unlimited amounts of data in JSON format both into Objective-C, as well as return structured data.

In my Javascript I used jquery, since I was already using it, but it should be pretty easy to rewrite it without jquery.

Well, here we go:

ConsoleURLProtocol.h:


ConsoleURLProtocol.m:


The Javascript. You'll want to call interceptErrors() from your document ready handler.
function interceptErrors() { // Override console.log console = new Object(); console.log = function(log) { sendToConsoleLogger({k:'c', m:log}); }; console.debug = console.log; console.info = console.log; console.warn = console.log; console.error = console.log; // Override window onerror window.onerror = function(error, url, line) { sendToConsoleLogger({k:'e', m:error, u:url, l:line}); }; } function sendToConsoleLogger(requestData) { $.ajax({ type: 'POST', url: '/_ConsoleLogger', dataType: 'json', data: JSON.stringify(requestData), success: function(responseData) { }, error: function(error) { }}); }


You need to register the URL protocol, presumably in AppDelegate.m, in didFinishLaunchingWithOptions.
[NSURLProtocol registerClass:[ConsoleURLProtocol class]];

In my case, the Javascript isn't even downloaded from the web, it's embedded in the app. And I'm only using this during debugging. Still, when calling from Javascript into Objective-C make sure you take precautions to not allow the Javascript to execute anything it shouldn't. Fortunately, since the Objective-C side is written like you'd write a web service, writing it defensively should be straightforward.

iOS UIWebView for files in bundle without file URLs

I had this little project where I essentially wanted to embed a HTML5/Javascript/CSS web site into an iPad app. The main consideration is that it had to run offline and work for data entry, though later on you could connect to the Internet to upload the data and do additional processing. Yes, for a mass-consumption, high performance app you really have to go native Objective-C, but for the purposes of this app, using a UIWebView would be fine even if it's a little slow.

The first logical choice was to use file:// URLs, and this almost works. The first annoyance comes when you want upload data, where you now need to implement CORS on the server because the request is coming from a source that's not in the same domain (cross-site scripting problem).

The second problem is that HTML5 local storage doesn't work quite right with file URLs either. That problem is rooted in the fact that there isn't a domain, and normally local storage is domain-based.
Actually, I think this might work in an iOS app and Safari, but when I tried it I had another bug that was preventing it from working properly.

In any case, even without this problem this technique is handy, because it mostly eliminates the need for special cases in the Javascript code.

After doing some poking around, I found two interesting solutions:
Rob Napier's RNCachingURL Protocol
AFCache

RNCachingURLProtocol is sort of the opposite of what I wanted, it makes requests and caches the result locally, which, while useful in some cases, isn't what I wanted. I wanted to always use the version embedded in the main bundle of my app. But I did look at that code quite carefully, and it was integral to my understanding how to do my solution.

AFCache seems like it might do what I wanted, but it's big, complicated, and hasn't been updated in a while. I wanted something small, fast, and ARC-aware.

So here's my solution:

I created two Objective-C classes:

LocalFileURLProtocol: This implements the NSURLProtocol to intercept the requests for files that are in our bundle. Note that we don't use file:// URLs, we use the actual http:// URLs we'd use for online use, but intercept them and return the html/css/js from our app's bundle instead of loading across the network.

LocalURLDecoder: This embeds our local knowledge of our server and URL format, so we know what things to try to intercept. You'll obviously need to customize this for your own app, but it's pretty straightforward.

The only thing you need to do is register the LocalFileURLProtocol, probably in AppDelegate.m in didFinishLaunchingWithOptions:
[NSURLProtocol registerClass:[LocalFileURLProtocol class]];


That's it! Here are the files:

LocalFileURLProtocol.h:


LocalFileURLProtocol.m:


LocalURLDecoder.h:


LocalURLDecoder.m:




YouTube "Playback Error" (iOS)

I was recently afflicted by the dreaded YouTube "Playback Error" on most of the iOS devices in my house, including two iPhones. My nephew's iPad was afflicted, but mine did not appear to be, at least when I initially tested it.

I found a solution, but honestly it's not entirely likely to be the problem anyone else is experiencing. Unless, of course, you're in an organization where the vast majority of your iOS devices are exhibiting this behavior, then keep reading.


It was not specific to the YouTube app, at least on my iPhone 5. If I went to youtube.com in Safari and tried to watch a video, it did not seem to start. The YouTube app failed pretty quickly with "Playback Error" usually, but I've also seen it fail to start playing, with the little waiting circle thingie spinning for what appears to be forever.

YouTube in Safari on my MacBook Air laptop, however, worked fine, and it was using the same Apple Airport Express Wi-Fi access points as the iPhones.

And, to complicate things further, all of the devices worked properly at Mom and Dad's. They use an Apple Airport Express access point and an Apple Airport Extreme that is a Wi-Fi access point, as well as being the firewall, router and DNS.

I tried several of the most popular and what to watch videos listed in the app, and all of them experience this problem. Note that many of these have a pre-roll advertisement, which may or may not be significant. While searching for advice on this problem, this was mentioned in a few postings.

Of course everything has been rebooted at least once. All software was updated to the latest version as of August 31, 2013. iOS 6.1.4, YouTube 2.0.0.7804. AirPort 7.6.4.

My great breakthrough occurred when I finally got a good packet capture of what's going on when a playback error occurs. 192.168.2.200 is my iPhone 5 and I started the capture just before playing one of the recommended videos for the first time, and a playback error occurred almost immediately. Here's a picture of the trace in Wireshark.

playback_error.jpgIf you click on the picture it will open in a larger window which is presumably easier to read.

Anyway, what you can see is that the problem appears to be DNS related, which makes some sense. The iPhone is rejecting some of the DNS responses from my router. YouTube/iOS sends out an unusually large number of concurrent DNS queries, 9, in fact, 4 of which are duplicates. Several of these fail to resolve because iOS stops listening on the receiving port, which causes an ICMP destination unreachable message to be sent back to the DNS server (192.168.2.1).

In my case, I have a Netgear FVS-124 dual WAN router that acts as my border router and firewall. It also served as my DNS server. I've had mysterious hard-to-reproduce problems using this as a DNS server in the past, and, well, for whatever reason, it apparently decided to cause problems again.

I ended up installing a new recursive DNS server inside my firewall on a Linux server and pointed all of the access points to use that as the DNS for DHCP.

Important tip: Even though I changed the DNS sever IP address on the access points, iOS did not recognize the change. It continued to point at the old router-based DNS until I forgot the Wi-Fi network and logged back into it again.

And, wonder of wonders, everything then worked perfectly! Every video played exactly as expected on every device.


Since I really only worked around the problem, I don't exactly know the root cause. But I'll put some blame on YouTube for needing to query so many different host names to play a video, the iOS resolver for not coalescing queries for the same host in rapid succession before a response comes back into one request and some to the Netgear router, which is obviously doing something differently than Linux to confuse iOS, which may or may not be the fault of iOS.

In any case, if a large number of iOS devices in your organization are experiencing "Playback Error" in YouTube, stick a packet analyzer between the devices and your local DNS and see if you're getting a similar packet trace. If so, you're experiencing something similar to what I was seeing and you should look toward something DNS related.

Fun with servo motors!

servo1.jpg
I thought it might be fun to play around with some servo motors. I never really did anything with them before, and they could come in handy for building mechanical things.

My experiment basically has two controls. When you turn the potentiometer, the left servo moves to the corresponding position. When you press or hold the left button, the right servo turns to the left. The center button moves it to the right. Fun stuff!




While the Raspberry Pi has two PWM outputs that could control the servos directly, I got an Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface to make it easy to control up to 16. Plus, it has nice header pins that fit standard servo motor connectors.


It just requires a little soldering to mount the headers and a 5VDC power supply to power the servos. Quick and easy.

elec1.jpgAdafruit has a nice tutorial on how to set everything up too. You may need to enable I2C support in Linux, as by default it's off in Raspbian distributions, but it's easy to enable.


I got a couple micro servos and a continuous rotation servo from Adafruit to play with, too.


I knew that they used servo motors in things like RC boats and airplanes to control things like rudder, flaps, etc. but I didn't really know how they worked. This tutorial helped.

The basic idea is that they generally have limited range (say, 180° of rotation), offer relatively precise positioning, and can hold that position (with some limitations).

There are only three wires to a servo motor, however, and two of them are power (5 to 6 VDC and ground). The third wire is used to communicate position.

It uses pulse-width modulation. You need to send the servo a pulse 50-60 times a second to keep or change its position. The width of the high part of the pulse determines the angle. For example, 1 millisecond would be 0°, 1.5 ms. would be 90°, and 2 ms. would be 180°. The rotation amounts vary depending on the servo, but 1 ms. = full left and 2 ms. = full right, whatever that might be.

The reason for this wacky encoding method is that it's easy to time division multiplex these signals very across a single radio frequency link. It allowed early RC planes and such to implement the controls entirely with inexpensive discrete components, without even any microcontrollers!


On the breadboard above the leftmost thing is the PWM driver and the thing next to it, with the big ribbon cable, is the Pi Cobbler, that allows access to the Raspberry Pi I/O pins.


The PWM driver just needs connections to power (3.3V), ground and the SDA and SCL lines on the Pi Cobbler board for the I2C interface. Also, an external 5 VDC power supply, and connections to the servo motors.


The next thing on the breadboard is an MCP3008, 8-channel, 12-bit, SPI interface Analog to Digital Converter (ADC). The Raspberry Pi doesn't include any analog inputs, but it's easy to add them with a MCP3008.

MCP3008.jpgPin 1 CH0 - Connect to the center tap of a potentiometer
Pin 9 DGND - Connect to ground
Pin 10 CS - Connect to Pi Cobbler CE0 (GPIO8)
Pin 11 DIN - Connect to Pi Cobbler MOSI (GPIO10)
Pin 12 DOUT - Connect to Pi Cobbler MISO (GPIO9)
Pin 13 CLK - Connect to Pi Cobbler SCKL (GPIO11)
Pin 14 AGND - Connect to ground
Pin 15 VREF - Connect to 3.3V
Pin 16 VDD - Connect to 3.3V

The newer Raspbian distros include kernel SPI support, but the Adafruit example doesn't use it, and for what I was doing I didn't need high performance, so I just use the Adafruit Python sample code that bit bangs the SPI for the MCP3008.


The next things on the breadboard are LEDs, one red and one green. The red is connected to GPIO22 and green to GPIO23. The short lead on the LED connects to ground via a 330 ohm resistor. The LEDs and resistors are included in the Adafruit Raspberry Pi starter kit. The software included here doesn't use them, however.


There is a 10K linear potentiometer. One side connects to ground, the other to 3.3V, and the center tap connects to the ADC. The MCP3008 is a 12-bit ADC, so it returns values from 0 to 1023 in the software. This particular pot fits nicely in the breadboard.


Lastly, I put 3 microswitches, also included in the Adafruit kit. The Raspberry Pi includes built-in software support for enabling weak pull-up or pull-down resistors on input lines, but as far as I can tell, this is not implemented in the Python library. WiringPi supports it. Anyway, I didn't want to go digging into the Python library code, so I just put 10K pull-down resistors on the switch inputs. I set them up as:

Left GPIO4
Center GPIO24
Right GPIO17

servo2.jpg
That's all the hardware, the rest is all fun in software!


The potentiometer and ADC are linked to one of the servos in software. When you turn the pot to left, the servo moves to the left. To the right, the servo moves to the right. It's pretty cool, and just a few lines of code!

        pot = mcp3008.readadc(POT_ADC_CHAN, SPICLK, SPIMOSI, SPIMISO, SPICS)
        if lastpot != pot:
            # Pot range 0 - 1023, scale to SERVO_MIN to SERVO_MAX
            # Also subtract pot from 1023 otherwise it rotates backward 
            val = (1023 - pot) * (SERVO_MAX - SERVO_MIN) / 1024 + SERVO_MIN
            pwm.setPWM(0, 0, val)
            lastpot = pot


I linked the other servo to the left and center switches. When you hit the left button, it moves left. When you hit the right button it moves right. And you can hold down the button.

        if GPIO.input(LEFT_SW) == 0:
            if servo1 > SERVO_MIN:
                servo1 = servo1 - 1
                pwm.setPWM(1, 0, servo1)

I haven't done anything with the right switch or the LEDs, but that's just a simple matter of software...


Here's the full source (tar/gzip archive, 5K). It's just a bit of test code that I threw together, so it's not cleaned up and prettified. Execute ServoTest.py in Python.


I should point out that I think you could probably make a potentiometer control a servo motor using a 555 timer IC and a few resistors and capacitors (maybe a diode too) to do the PWM. But that is not nearly as fun!