HOWTO: Write iSync Phone Plugins

The information on this page is outdated, and kept for historical purposes only, since when I started digging into this there was absolutely no public information available. See the update block below for more info.

Nov 17th: Far too many people have written to me to point out that the Xcode version that ships with Leopard has an application to help you create your own plugins to leave this page without any sort of update, so here is, in a nutshell, what I know about it:

  • It is called "iSync Plug-in Maker" and lives in /Developer/Applications/Utilities
  • It is basically a wizard that not only makes it extremely easy to set AT strings, define SyncML parameters, etc., and making basic checks, but it can also lookup USB or Bluetooth device identifiers and pack in extra scripts (for modem use and the like).
  • It has, for the first time, something approaching actual documentation of what iSync plugins do, and what you need to to to test them.
  • It creates plugins for iSync 2.3 or above, wrapping them up in a disk image for distribution.
  • It does not mean you can create a plugin from scratch without knowing some of the basics about how phones work.

It is an amazing step forward as far as creating plugins is concerned, and makes me happy that Apple decided to do something about the state of affairs this page documented. The text will eventually be revised to reflect this.

Apr 9 Update: Nokia has recently started making their own plugins available for free.

Previous Update: With the release of iSync 2.3 as part of Mac OS X 10.4.7 (and with the previous release of iSync 2.2), the information on this HOWTO is undergoing significant revision. See this post and this other post for some background as I tried to figure out the changes, and keep in mind that this is a living document (check back often).

Anther resource you might find useful is the DIY iSync Phone Plugin site (which is also slightly outdated).

Given that there seem to be a lot of misconceptions about exactly how iSync detects and understands the capabilities of mobile phones and pretty much no written documentation about the whole process, I thought it was about time I published my notes on the matter.

By early 2006, Apple was starting to lag more than six months behind the handset market, and (worse of all as far as I'm concerned) it had very little support for European models. Since I go through phones like most people go through books, after a while (and a lot of trial and error) I eventually figured out how to write plugins for most of the phones I had.

As iSync evolved internally in leaps and bounds a lot of this was in a state of flux, so it wasn't until up to version 2.1.1 (89.0) - the one included with the latest Tiger 10.4.3 updates - that I thought this was stable enough to publish.

With the release of iSync 2.2 (180.0) as part of the Tiger 10.4.6 update, all plugins became obsolete overnight, causing a significant re-write of this document.

With the release of iSync 2.3 (500.86) as part of the Tiger 10.4.7 update, mostly the same happened, even if in a minor scale. Nevertheless, most of the document appears to still be valid.

If this seems rather ad-hoc and lacking in some areas, please bear in mind that I haven't, to this day, found any comprehensive documentation on this (although I hope that one day Apple will publish some and encourage handset manufacturers to distribute plugins themselves), so these notes are based on trial, error, and judicious use of logs and ktrace.

Let's start with the basics, then:

What's an iSync Plugin?

Well, it's a special package in the traditional Mac OS X format (i.e., a specially named folder with a few bits of XML thrown in), and it usually resides in ~/Library/PhonePlugins or /Library/PhonePlugins.

A few people seem to think that PhonePlugins only work inside the top-level Library, but I've seen iSync 2.2 look in several directories (the full list is near the bottom of this page), so you can test them under your own user tree and only install them globally when other users need them.

But what's inside, then? Well, that's easy enough - the following is a file tree for a minimal plugin:

PhonePlugins -+- VendorModel.phoneplugin
              .  |
              .  +- Contents
              .     |
                    +- Info.plist
                    +- Resources
                       |
                       +- MetaClasses.plist
                       +- VENDMODL.tiff

The .plist files are where all the important information is, of course - let's look at Info.plist first:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleDevelopmentRegion</key>
  <string>English</string>
  <key>CFBundleExecutable</key>
  <string>VendorModelPlugin</string>
  <key>CFBundleIdentifier</key>
  <string>com.vendor.model.uniqueidentifier</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundleName</key>
  <string>VendorModelPlugin</string>
  <key>CFBundlePackageType</key>
  <string>BNDL</string>
  <key>CFBundleShortVersionString</key>
  <string>2.2</string>
  <key>CFBundleSignature</key>
  <string>????</string>
  <key>CFBundleVersion</key>
  <string>424.0</string>
  <key>IPHPhoneConduitVersion</key>
  <string>400.0</string>
</dict>
</plist>

As you can see above, this .plist denotes a plugin for a specific phone model. One of the mistakes I made early was to think that Info.plist denoted the kind of plugin - now I know that it supplies a set of unique identifiers for loading the bundle, and that the Metaclasses.plist file can provide support for an arbitrary set of phone models.

The IPHPhoneConduitVersion is of special note, since iSync 2.2 refuses to load any plugin without this value set to (at least) 400.0 (i.e., lower values don't work), presumably to match the internal conduit versions (I have not been able to determine whether this is dependent on the phone family or not, but my guess at this point is that it's not).

If you look inside iSync.app, you'll notice that the application itself includes phone family and phone model plugins, which is where you'll find a bunch of phone icons as well (obviously, VENDMODL.tiff in the example above is the phone icon you'll see displayed on iSync).

Icons are the easiest bit - right-click on iSync and choose "Show Package Contents" to navigate your way inside, and you should find them easily enough in a Resources directory under one of the built-in plugins. They are all TIFF files with the appropriate alpha channel information, and it is easy to work from them to create anything you might need.

And now let's look at a MetaClasses.plist file for a bogus model:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>com.vendor.modelX</key>
  <dict>
    <key>Identification</key>
    <dict>
      <key>com.apple.cgmi+cgmm</key>
      <string>Vendor, Copyright 2005+"GSM","WCDMA","MODEL=X"</string>
      <key>com.apple.gmi+gmm</key>
      <string>Vendor, Copyright 2005+"GSM","WCDMA","MODEL=X"</string>
    </dict>
    <key>InheritsFrom</key>
    <array>
      <string>com.vendor.switchable-usb-bt.0x1000/0x2000</string>
      <string>family.com.vendor.usb-bt</string>
    </array>
    <key>Services</key>
    <array>
      <dict>
        <key>ServiceName</key>
        <string>com.apple.model</string>
        <key>ServiceProperties</key>
        <dict>
          <key>ModelName</key>
          <string>X</string>
          <key>PhoneIcon</key>
          <string>VENDMODL.tiff</string>
        </dict>
      </dict>
      <dict>
        <key>ServiceName</key>
        <string>com.apple.synchro</string>
        <key>ServiceProperties</key>
        <dict>
          <key>stringEncoding</key>
          <string>UCS2</string>
        </dict>
      </dict>
    </array>
  </dict>
</dict>
</plist>

We can ignore the Services branch for now (most of it should be easy to understand, but I have yet to compile a comprehensive index of service names and properties).

Do note, however, the InheritsFrom clause and the way that it indicates that this model inherits its characteristics from a family of models as well as a specific USB identifier. This is something introduced by iSync 2.2, apparently to make clearer the distinction between devices and device families.

But let's focus on the Identification portion and see what those strings actually are, shall we?

So, How Does iSync Detect My Phone?

Well, that's easy once you've spent some time dealing with modems. You see, there are two sets of AT Commands that, when invoked on a modem (which are an integral aspect of most modern phones), allow you to determine exactly what is on the other end of the serial port.

Via trial and error, I have established that iSync enumerates USB and Bluetooth devices active nearby (starting with those you've previously paired), queries them for serial port capabilities and issues them the following AT Commands (comments in parenthesis):

ATE1 (force echo on)
OK

AT+GMI (vendor info)
+GMI: Vendor, Copyright 2005

OK
AT+GMM (model info)
+GMM: "GSM","WCDMA","MODEL=X"

I've seen it do both GMI/GMM and CGMI/CGMM on occasion (the two sets of commands are equivalent for this purpose), but as you can guess by reading the .plist above these are precisely the strings that are tied together with a plus (+) sign above.

The example was actually designed to show that you have to include the quotation marks as well - most phones don't reply with a list of attributes like the +GMM reply above, that's actually a Motorola trait.

What I don't know at this point is how it uses the USB/Bluetooth IDs before issuing the commands. I assume they are used to narrow down the phone model both due to the way MetaClasses.plist is structured and because I've seen Bluetooth/USB device enumeration done before actually accessing the serial port.

Diving into .plists

The way in which iSync resolves the com.vendor.switchable-usb-bt.0x1000/0x2000 string is still a bit beyond me. At this point (and based on what I've picked up digging around in the bundled plugins) I have to assume there is an implicit inheritance scheme that lets you resolve a phone model to a sync transport and then, eventually, to a conduit (and henceforth to a .dylib with the actual compiled conduit, etc.).

Most of those strings are defined in the built-in plugins inside iSync.app, namely in the ApplePhoneConduit.syncdevice folder - you just have to dig around a bit in the plugins' MetaClasses.plist files to track down a full inheritance chain.

But it is plain to see that 0x1000 is the Vendor ID and that 0x2000 is the Product ID (which you can obtain on the USB bus by using System Profiler or its CLI counterpart, system_profiler), and that is actually what you need for most simple tinkering (i.e., to build a plugin for a variant of an already-existing device).

A new twist I've noticed in iSync 2.2 is the inclusion of what appears to be JavaScript inside MetaClasses.plist:

<key>ServiceProperties</key>
  <dict>
    <key>ModelSwitchScript</key>
      <string>
          function parseGMR(properties) {
              gmr = properties["com.apple.GMR"];
              application.log("Testing revision from GMR: " + gmr);
              if(gmr != null) {
                  match = utils.match(gmr, "V\\s*([0-9]+\\.[0-9]+)\\s+[^\\s]+\\s+([A-Z]+)-([0-9]+) ");
                  if(match == null) {
                      application.log("Can't parse GMR");
                      return null;
                  }
                  if(match.length != 3) {
                      application.log("Bad parsing of GMR");
                      return null;
                  }
                  parsed_version = {};
                  parsed_version['generation'] = match[1];
                  parsed_version['generation_version'] = (match[2] - 0);
                  parsed_version['version'] = (match[0] - 0.0);
                  application.log("Parsed version is:\n" + utils.objcDescription(parsed_version));
                  return parsed_version;
              }
              return null;
          }
          ...

The above is a section of iSync 2.2's MetaClasses.plist pertaining to the Nokia 6230i, a Series 40 device.

Although I have absolutely no idea as to exactly what functions you can define and what sort of runtime they are evaluated in, note the properties array, the utils object and the application.log call - all of these are extremely interesting insights into to the way iSync works, and hint at the possibility of manipulating sync attributes and sending AT Commands to the phone on the fly...

Base Conduits

On inspection, there seem to be base conduits for Siemens, SonyEricsson and Motorola native sync protocols or transports (i.e., things like OBEX, IrMC, etc.) as well as for higher-level and more complex stuff like SyncML (for Symbian) and Palm sync protocols.

Bear in mind that handsets are not all alike - for instance, Nokia currently has three different main platforms (Series 40, Series 60 and Series 80, which is shifting into something a bit different), and although iSync 2.2 added support for some Series 40 phones, you'll soon notice that there are several minor variants of each kind of device based on year, firmware revisions, and, yes, bugs (just search for obexbug, or notice the number of serie60vX.Y variants...)

Another example: SonyEricsson's two platforms (EMP, which drives the V600i, and Symbian, which drives their P-series of smartphones) are both supported, but Sharp phones (most of which are also EMP) are not. Motorola phones have an even wider range of (non-)support, even though they seem to be a large portion of the *.switchable-usb-bt.* models.

Your mileage will vary significantly, and you have to know quite a bit about phone platforms to get anywhere.

Furthermore, each conduit seems to hold definitions for what precisely defines a calendar item, a to-do item, etc. in each family of handsets. For instance, you can easily spot that to-dos have priorities, what are the maximum widths of fields, whether or not to replace whitespace in phone contacts with dashes, etc., and what are the allowed mappings between the phone and the Mac.

This information alone is likely to be of interest to people who (like me) would prefer SonyEricsson phones to not include dashes to join first and middle names, or who would like to change the resolution of the contact thumbnails sent to Symbian devices (it seems to be in there, although I can't find it right now).

Building Your Own

Well, the easiest way is to start from an existing one. You can start with my Nokia/9300 one, which I blogged about here. It's a Series 80 device, so it can use the SyncML conduits set up for Series 60 devices.

But before you go about hacking it to pieces, you should start by diving into the innards of iSync's built-in plugins to see whether a similar model is already supported - in most cases you only have to choose a phone family, copy the relevant sections into your new MetaClasses.plist, tweak the AT Commands' output (and the USB id), change the icon, and you're done.

However, it is seldom that simple - unless you're dealing with SonyEricsson or Nokia devices, you're likely to spend a good while trying to figure out the relevant parameters, and it may well happen that your phone requires a conduit that doesn't even exist yet.

But here's a short list of best practices to keep in mind while you try -

  • Choose unique identifiers for the CFBundleExecutable, CFBundleIdentifier and CFBundleName in your Info.plist, so as not to clash with other plugins.
  • Put your test plugins in /Users/you/Library/PhonePlugins instead of mucking about with the top-level Library
  • Get ZTerm and use it to determine the right AT Commands output.
    • a few people have pointed out that screen can be used as a "poor man's minicom", but that usually requires knowing which serial port to connect to. screen /dev/tty.Bluetooth-Modem will work, but when you hook up via USB, you need to figure out the serial port, and ZTerm gives you a nice little list.
  • Try using USB first and use System Profiler to determine the Vendor ID and Product ID for your phone.
  • Open the Console and watch console.log - that's where iSync will log any errors while processing your plugins.
  • There is no need to reboot. Simply re-launch iSync to force it to re-scan the plugins.
  • When in doubt about whether your plugin is being parsed at all, remove one of the closing XML tags and re-launch iSync - it will log a complaint about not being able to parse the .plist.

And please remember that -

I strongly advise against editing iSync internal files directly. That's about as subtle as performing eye surgery with boxing gloves, and can result in a completely broken iSync application. By all means read the existing files, but don't overwrite them.

Search Paths

After using ktrace on the iSync 2.2 binary, I have determined that the search order it uses to find plugins is:

"/Developer/PhonePlugins"
"/System/Library/PhonePlugins"
"/Network/Developer/PhonePlugins"
"/Network/Library/PhonePlugins"
"/Library/PhonePlugins"
"/Users/user/Developer/PhonePlugins"
"/Users/user/Library/PhonePlugins"

...which is interesting, since it hints at provision for some sort of staging/debugging process. With luck, we'll see some official Apple developer documentation yet.

And that's it for now.

Do bear in mind that the only way to actually test these is by having the phone - so if you're stuck with any of the steps above, I can't really help you, and there is really no point in asking me to put together a plugin for your phone.

Instead, try searching my site for your phone and seeing if I have a link to a suitable plugin, or check sites like Mac OS X Hints for some of the .plist bits and pieces that people have posted in the meantime and try rolling them into a standalone plugin.

But if you found this useful nonetheless, please consider donating towards my gadget money. :)