Scott Hanselman

Coding4Fun Hardware Boneyard - Using the CueCat with .NET

September 16, 2006 Comment on this post [7] Posted in Coding4Fun | Programming
Sponsored By

In an ongoing Mardi Gras celebration of the soon-to-happen Coding4Fun Redesign, I got permission from the rockin' awesome Dan Fernandez to published a Coding4Fun article here on Hanselman.com as an exclusive. This article will be republished on the new redesigned site very soon, along with a "Coding with the FingerPrint Reader" article and possible a fabulous super-secret IR/Robot/PowerShell article.

Dig up your CueCat and enjoy.


Coding4Fun Hardware Boneyard - Using the CueCat with .NET

Summary: In this rogue installment of "Some Assembly Required" column, Scott Hanselman borrows Travis Illig's CueCat BarCode scanner and creates a plugin for Windows Live Writer than lets him blog more easily about books he's reading. We decode the bar code info, change UPCs into ISBNs, call Amazon's Web Service via REST and integrate with Windows Live Writer all in one article. Whew!

The CueCat BarCode Scanner

180px-Cuecat1Here's what Wikipedia has to say about the CueCat BarCode scanner:

The :CueCat is a cat-themed handheld barcode reader developed in the late 1990s by the now-defunct DigitalConvergence Corporation, which connected to computers using the PS/2 keyboard port and later USB. The :CueCat enabled users to link to an Internet URL by scanning a barcode appearing in an article, catalog or on some other printed matter. In this way a user could be directed to a web page containing related information. The system that supported this functionality is no longer in operation.

Ah, nothing like the smell of obsolete hardware to get me thinking. You can get CueCats all over, there are millions of them, including on ebay. I asked around and my buddy Travis Illig loaned me his. It's ripe for hacking.

Decoding the CueCat Output

The CueCat speaks a funky encoded format that it "types" as if it were a keyboard. For all intents, it IS a keyboard as the PS2 version we're using is installed in series with your existing (if you have one) keyboard.

For example, my copy of Neil Gaiman's excellent "Stardust" produces this output:

.C3nZC3nZC3nZE3r0Chr3CNnY.cGf2.ENr7C3n1C3PWD3rYCxzYChnZ.

Which can be broken up into a series of tokens, some are one character and some are two. The periods are delimiters, so we'll toss them for now.

 C3 n Z C3 n Z C3 n Z E3 r 0 Ch r 3 CN n Y EN r 7 C3 n 1 C3 P W D3 r Y Cx z Y Ch n Z

The first part is the serial number of CueCat itself, which freaked a log of privacy folks out in the past. The scanned barcode is actually the last part after the third . delimiter:

EN r 7 C3 n 1 C3 P W D3 r Y Cx z Y Ch n Z

Each of these tokens is a key to a lookup table. The encoding is pretty lame, and very arbitrary, but it confounded folks for a few hours at least. There's dozens of implementations of this decoding to be found on the Web, although none in .NET, which is where we come in. Here's a resource page that includes several implementations in C, Perl and Python, http://www.beau.lib.la.us/~jmorris/linux/cuecat/ but it hasn't been updated in at least six years. There's also some very old but interesting discussion here http://cexx.org/cuecat.htm.

The lookup table looks like this (a C# example):

static Decoder()

{

    decodingTable = new Dictionary<string, int>[3];

    decodingTable[0] = new Dictionary<string, int>();

    decodingTable[1] = new Dictionary<string, int>();

    decodingTable[2] = new Dictionary<string, int>();

 

    decodingTable[0]["Z"] = 0;

    decodingTable[0]["Y"] = 1;

    decodingTable[0]["X"] = 2;

    decodingTable[0]["W"] = 3;

    decodingTable[0]["3"] = 4;

    decodingTable[0]["2"] = 5;

    decodingTable[0]["1"] = 6;

    decodingTable[0]["0"] = 7;

    decodingTable[0]["7"] = 8;

    decodingTable[0]["6"] = 9;

 

    decodingTable[1]["C3"] = 0;

    decodingTable[1]["CN"] = 1;

    decodingTable[1]["Cx"] = 2;

    decodingTable[1]["Ch"] = 3;

    decodingTable[1]["D3"] = 4;

    decodingTable[1]["DN"] = 5;

    decodingTable[1]["Dx"] = 6;

    decodingTable[1]["Dh"] = 7;

    decodingTable[1]["E3"] = 8;

    decodingTable[1]["EN"] = 9;

 

    decodingTable[2]["n"] = 0;

    decodingTable[2]["j"] = 1;

    decodingTable[2]["f"] = 2;

    decodingTable[2]["b"] = 3;

    decodingTable[2]["D"] = 4;

    decodingTable[2]["z"] = 5;

    decodingTable[2]["v"] = 6;

    decodingTable[2]["r"] = 7;

    decodingTable[2]["T"] = 8;

    decodingTable[2]["P"] = 9;

}

 

private static Dictionary<string, int>[] decodingTable;

See how some tokens are two chars and some are one? We'll run through the tokens testing to see if they are in each dictionary and creating the result. For my Gaiman book, we end up with:

978006093471251300

Now what do we do with it?

UPCs and ISBN

Universal Product Codes or UPCs are different from ISBNs. This page at the Book-Scanning Project gave me the algorithm I needed to convert a UPC code into an ISBN that I could use to talk to Amazon.com. Most books have a bar code with 13 digits starting with "978." Turns out that the first 3 digits of a UPC are the country of origin, but "978" is "Bookland." Apparently the UPC number for a book is called the "Bookland EAN."

Here's the Javascript taken from http://isbn.nu.

if (indexisbn.indexOf("978") == 0) {
   isbn = isbn.substr(3,9);
   var xsum = 0;
   var add = 0;
   var i = 0;
   for (i = 0; i < 9; i++) {
        add = isbn.substr(i,1);
        xsum += (10 - i) * add;
   }
   xsum %= 11;
   xsum = 11 - xsum;
   if (xsum == 10) { xsum = "X"; }
   if (xsum == 11) { xsum = "0"; }
   isbn += xsum;
}

Now, converted into C#

public static string ConvertUPCtoISBN(string code)

{

    if (code.StartsWith("978") == false)

    {

        throw new ArgumentOutOfRangeException("UPCs that might be books have length 13 and start with 978");

    }

    code = code.Substring(3,9);

    int xsum = 0;

    for (int i = 0; i < 9; i++)

    {

        xsum += (10 - i) * int.Parse(code[i].ToString());

    }

 

    xsum %= 11;

    xsum = 11 - xsum;

 

    String val = xsum.ToString();

 

    switch (xsum)

    {

        case 10: val = "X"; break;

        case 11: val = "0"; break;

    }

 

    code += val;

    return code; //Now an ISBN

 }

So, my Gaiman UPC of 978006093471251300 turns into an ISBN of 0060934719. This ISBN can be used to talk to Amazon. Note that the source code to this article includes both C# and VB.

Calling Amazon's REST Web Service

Amazon.com has a rich Web Services platform that includes formal SOAP-based Web Services, but for our project, their HTTP GET-based system will work nicely. I visited the Amazon Web Services Home Page and applied for a free Amazon Web Service Developer Key. You'll need to do the same if you want to get the code to work. Amazon has some create documentation on their site that makes this really easy.

I'll make an HTTP GET call to Amazon like this (broken up for readability):

http://webservices.amazon.com/onca/xml?
Service=AWSECommerceService&
AWSAccessKeyId=YOURAWSKEYHERE&
Operation=ItemLookup&
ItemId=0060934719&
IdType=ASIN

This HTTP GET will result in an XML document as seen in this screenshot.

Hanselmancuecataws

Ah! Just the kind of information I was looking for.

A CueCatConsole

Before we integrate with Windows Live Writer, let's make a little test console to make sure this all works together. I put the CueCatDecoder into a small reusable assembly and called it from the Console Application. I also pull the Amazon Web Services key from the configuration file. Note that VS.NET 2005 creates a strongly typed accessor for the "AWSAccessKeyId" automatically.

I've removed the error handling in this listing for clarity.

CueCatDecoder.Product product = null;

product = CueCatDecoder.Decoder.Decode(input);

Console.WriteLine("Type: {0}.", product.Type.ToString());

string amazonType = "ASIN";

string amazonUrl = String.Format("http://webservices.amazon.com/onca/xml
      ?Service=AWSECommerceService&AWSAccessKeyId={0}
      &Operation=ItemLookup&ItemId={1}
      &IdType={2}
      &SearchIndex="
,

            Properties.Settings.Default.AWSAccessKeyId,

            product.ID,

            amazonType);

 

System.Net.WebClient web = new WebClient();

string amazonData = web.DownloadString(amazonUrl);

XmlDocument xmlDocument = new XmlDocument();

XmlNamespaceManager xnm = new XmlNamespaceManager(xmlDocument.NameTable);

xnm.AddNamespace("def", "http://webservices.amazon.com/AWSECommerceService/2005-10-05");

xmlDocument.Load(new System.IO.StringReader(amazonData));

XmlNode itemNode = xmlDocument.SelectSingleNode("/def:ItemLookupResponse/def:Items/def:Item", xnm);

Console.WriteLine(String.Format("FOUND a {0} called {1} by {2}",

        itemNode.SelectSingleNode("//def:ProductGroup", xnm).InnerText,

        itemNode.SelectSingleNode("//def:Title", xnm).InnerText,

        itemNode.SelectSingleNode("//def:Author", xnm).InnerText));

Notice that since the Amazon XML includes a default namespace, we have to let SelectSingleNode know about that namespace by registering a namespace prefix - in this case "def:" - that we use later in our XPath. There are many ways to query XML within the .NET Base Class Library that are faster, but for a small application like this the XmlDocument is a reasonable choice when performance doesn't matter.

Here's the output from my CueCatConsole:

Coding4Fun: CueCatDecoder by Scott Hanselman @ http://www.hanselman.com
Scan item with CueCat now or press Q then Enter to exit
.C3nZC3nZC3nZE3r0Chr3CNnY.cGen.ENr7C3T7D3f0C3vYDW.
Type: ISBN.
FOUND a Book called The Goal: A Process of Ongoing Improvement by Eliyahu M. Goldratt
Scan item with CueCat now or press Q then Enter to exit
.C3nZC3nZC3nZE3r0Chr3CNnY.cGf2.ENr7CNz6C3z6DxvXChzXD3P6.
Type: ISBN.
FOUND a Book called Programming Sudoku (Technology in Action) by Wei-Meng Lee
Scan item with CueCat now or press Q then Enter to exit
.C3nZC3nZC3nZE3r0Chr3CNnY.cGf2.ENr7C3n1C3PWD3rYCxzYChnZ.
Type: ISBN.
FOUND a Book called Stardust by Neil Gaiman
Scan item with CueCat now or press Q then Enter to exit

Fantastic. It works. Let's integrate it with Windows Live Writer, the new Offline Blogging Editor in BETA (at the time of this writing) from http://www.live.com.

Integrating with Windows Live Writer

Windows Live Writer (download) is a new desktop application that folks with blogs can use to write their blog posts offline. I use DasBlog for my blog at http://www.hanselman.com, and since DasBlog supports the standard MetaWebLog API also supported by Live Writer, I was in business right away.

Windows Live Writer also has an SDK that supports "Content Source Plugins" to extend the capabilities of the application and let folks insert new kinds of data. That's exactly what I want to do! I'd like to click "Blog about Book" and have the book title and author appear linked with an image of the book cover. I'd also like have the Amazon.com URL to include my Amazon Associates ID so I could make a few cents if someone clicks my link and buys the book.

Writing a plugin for Windows Live Writer couldn't be easier. The SDK includes step-by-step instructions. I created a class that derived from WindowsLive.Writer.Api.ContentSource and overrode the CreateContent method, returning my new snippet of HTML in the newContent string variable.

Here's the gist of the source, again with error handling removed for clarity. VB is included in the download.

using System;

using System.Collections.Generic;

using System.Windows.Forms;

using WindowsLive.Writer.Api;

using CueCatDecoder;

 

namespace AmazonLiveWriterPlugin

{

    [WriterPlugin("605EEA63-B74B-4e9d-A290-F5E9E8229FC1", "Amazon Links with CueCat",

        ImagePath = "Images.CueCat.png",

        PublisherUrl = "http://www.hanselman.com",

        Description = "Amazon Links with a CueCat.")]

    [InsertableContentSource("Amazon Links")]

    public class Plugin : ContentSource

    {

        const string AMAZONASSOCIATESID = "amazonassociatesid";

        const string AMAZONWEBSERVICESID = "amazonwebservicesid";

 

        public override System.Windows.Forms.DialogResult CreateContent(System.Windows.Forms.IWin32Window dialogOwner, ref string newContent)

        {

            using(InsertForm form = new InsertForm())

            {

                form.AmazonAssociatesID = this.Options[AMAZONASSOCIATESID];

                form.AmazonWebServicesID = this.Options[AMAZONWEBSERVICESID];

                DialogResult result = form.ShowDialog();

                if (result == DialogResult.OK)

                {

                    this.Options[AMAZONASSOCIATESID] = form.AmazonAssociatesID;

                    this.Options[AMAZONWEBSERVICESID] = form.AmazonWebServicesID;

                    Product p = Decoder.Decode(form.CueCatData);

                    AmazonBook book = AmazonBookPopulator.CreateAmazonProduct(p, form.AmazonWebServicesID);

                    string associatesId = form.AmazonAssociatesID.Trim();

                    string builtAmazonUrl = "<a href=\"http://www.amazon.com/exec/obidos/redirect?link_code=as2&path=ASIN/{0}&tag={1}&camp=1789&creative=9325\">{2} by {3}</a><a href=\"http://www.amazon.com/exec/obidos/redirect?link_code=as2&path=ASIN/{0}&tag={1}&camp=1789&creative=9325\"><img border=\"0\" src=\"http://images.amazon.com/images/P/{0}.01._AA_SCMZZZZZZZ_.jpg\"></a><img src=\"http://www.assoc-amazon.com/e/ir?t={1}&l=as2&o=1&a={0}\" width=\"1\" height=\"1\" border=\"0\" alt=\"\" style=\"border:none !important; margin:0px !important;\" />";

                    newContent = string.Format(builtAmazonUrl, book.ID, associatesId, book.Title, book.Author);

                }

                return result;

            }

        }

    }

}

Notice the crazy Amazon.com URL. Most folks with blogs become Amazon Associates to make a little money to pay for hosting (you won't get rich doing this, believe me). The Amazon Associates site includes link builders that create these complex snippets of HTML. I selected the large image, basic HTML option, then inserted String.Format tokens like {0} in the appropriate places.

Installation is simple, just copy Coding4Fun.AmazonLiveWriterPlugin.dll and Coding4Fun.CueCatDecoder.dll to C:\Program Files\Windows Live Writer\Plugins and they will be automatically detected.

Our plugin appears automatically in the Windows Live Writer sidebar. Notice the text and the icon was picked up from the Plugin class attributes. My Amazon Web Services ID and Associates ID are both stored internally by an Options bag managed by Windows Live Writer, making management of preferences very easy for the plugin developer. Once the CueCat data is scanned, we decode it and make the HTTP GET Web Services call using our external CueCat assembly created earlier.

Hanselmancuecatplugin

Conclusion

There's things that could be extended, added, and improved on with this project. Here are some ideas to get you started:

  • Include support for CDs and other products available on Amazon.
  • Add support for more web-based databases with UPC codes.
  • Support different Amazon link styles. 
  • Create a system to print out product tags for a Garage Sale.

Have fun and have no fear when faced with the words - Some Assembly Required!

File Attachment: Hanselman Coding4Fun CueCat.zip (74 KB)


Scott Hanselman is the Chief Architect at the Corillian Corporation, an eFinance enabler. He has thirteen years experience developing software in C, C++, VB, COM, and most recently in VB.NET and C#. Scott is proud to be both a Microsoft RD and Architecture MVP. He is co-author of Professional ASP.NET 2.0 with Bill Evjen, available on BookPool.com and Amazon. His thoughts on the Zen of .NET, Programming and Web Services can be found on his blog at http://www.computerzen.com. He thanks his wife and 9 month old Z for indulging him in these hobbies!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
September 16, 2006 19:24
Scott... just a heads up on ISBN's and EAN's (aka 978 UPC's). The book world has essentially run out of ISBN's. So they are in the process of switching to using the EAN as the ISBN (some refer to it as the ISBN-13 as opposed to the ISBN-10). I believe that 979 or 977 or something like that is going to be used... essentially, the EAN isn't going to gain them 3 additional digits but it is going to gain them several sets of 10 digits... 978 plus an additional set or two (whatever they can reserve without bumping into issued manuf. ID's for UPC's).

I've had to code the EAN to ISBN stuff several times (since I work for a book retailer). Thanks for the great article!
ScW
September 16, 2006 21:07
Dude, this totally rocks. I so remember the CueCat (and the scandal associated with it). Never got my hands on one, but now seeing your article, I might snag on off eBay. What a great article as I thought I was just reading a "hacking the cuecat" blog, then you hooked in the Amazon web services, then the link to Live Writer. Sheesh. It was like watching a well scripted movie. Thanks!
September 17, 2006 8:44
Nice! My wife got a cuecat set to her when she worked in marketing for our company. I'll have to 'borrow' it and try this out!
Ian
September 18, 2006 2:07
What extra work would be involved in getting this working with a usb cuecat?
September 18, 2006 15:21
Thanx Scott... now I have to go buy me a :CueCat just so I can try this out. :P
September 21, 2006 2:16
I've long thought that MSFT should have just bought the remnants of Digital Convergence (which it could of gotten for about $1.50 towards the end), pitched their lame-brain business model, and just built the drivers and barcode fonts into the standard install of Windows. Soon bar-code readers would have been as common as mice (and probably mashed into the same device). Think what you could have done in that situation.....
October 23, 2006 6:17
I did exactly this last summer. The decoding was slightly different, but I had it hunt down amazon us, uk, and the library of congress. It's amazing the number of errors they have -- including some books that nobody has correct (or even congruent) information on.

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.