Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Hardware Boneyard - Using the CueCat with .NET

  In this 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!
Scott's Blog

Difficulty: Easy
Time Required: Less than 1 hour
Cost: Less Than $50
Software: Visual Basic or Visual C# Express Editions
Hardware: A CueCat - try eBay or your garage
Download: Download

Summary: In this 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

Here'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.

 

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.

 

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!


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 son Zenzo for indulging him in these hobbies!

Tags:

Follow the Discussion

  • MSDNArchiveMSDNArchive

    I wrote something like this for a web app in 2005 (using a CueCat).  One problem you need to watch out for is that Amazon US, Amazon UK, and the Library of Congress can pull up conflicting records for a given ISBN.  For a few books of mine, each of them was wrong.

    Lesson:  always check your results.

  • SwampthingSwampthing

    Digital Convergenced developed the Cue Cat.

    Neomedia Technologies licensed the technology and revamped it to meet today's mobile life style.

    Paperclick now called Qode if a free down loadable platfrom for mobile devices.

    Qode allows the user to navigate to content with their mobile device.

    Point and click and go direct to content on the mboile web.

    Click on the 1D, UPC, 2D, QR, datamatrix, trademark, logo, keyword, RFID, slogan, etc, and be redirected to the products or brands web page to get information, coupons, enter a sweepstakes, get a gift card (reference Starbuck's Summer Pursuit where customers would get $5 bucks back when they clicked ont he logo.)

    Navigation has become easier for all looking for it.

    Try that and Active Shopper mobile edition.

    All free.

  • JasonJason

    I can't compile because I don't have the correct reference to "InsertForm".  What am I missing?

    The type or namespace name 'InsertForm' could not be found (are you missing a using directive or an assembly reference?)

  • Clint RutkasClint I'm a "developer"

    @Jason, did a quick glance at the project, InsertForm is one of the classes in the project.  Namely in the AmazonLiveWriterPlugin class

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.