Uwatec Smart Protocol

This document contains my findings after investigating the   protocol for the Uwatec Smart and new Aladin dive computers which    use IrDA to transfer data to and from computers.

It is neither complete or correct, however it has allowed me to write a plugin to allow gdivelog to interact with said dive computers.

Setting up IrDA on Linux
This section is not a HOWTO, please see the [http://tuxmobil.org/Infrared-HOWTO/Infrared-HOWTO.html Linux Infrared HOWTO] if this is what you want, these are simply my notes, which you may or may not find helpful.

I used Ubuntu 5.10 and a stir4200 based USB to IrDA adaptor.

When I plugged in the adaptor, Ubuntu recognised it and loaded the stir4200 and irda drivers. lsmod revealed the following:

... stir4200              14372  0 irda                 187612  1 stir4200 ...

To bring the IrDA subsystem up:

$ sudo irattach irda0 -s

To check that it is running:

$ ps -A | grep ir

Which should return something like:

... 27421 ?       00:00:00 irattach 27424 ?       00:00:00 irda0 ...

Sometimes, I have not determined why or how, IrDA appears to stop working. To rectify this restart IrDA by killing irattach, unplugging and replugging the USB to IrDA adaptor and then using irattach again as above.

To test whether IrDA is up and running use irdadump from the irda-utils package.

$ sudo irdadump

When the dive computer is switched on and it&apos;s IrDA port is placed in range of the PC's IrDA port you should see something similar to the below as the output scrolls past:

Discovered: (40,1) daddr: ca5a9900 saddr: 9537c2d9  hints: 8000  name: Aladin Smart Pro

If you do not see this, IrDA is not working.

This is my entire knowledge of IrDA on Linux. I can be of no further assitance, so please do not ask. Sorry.

Connecting to the dive computer
Connections are made with the sockets interface.

sir_family should be set to AF_IRDA sir_addr should be set to the daddr returned during discovery sir_lsap_sel must be set to sir_name must be an empty string

Example C code:
 * 1) define MAX_DEVICES 10
 * 2) define NUM_TRIES 5

unsigned short int smart_discover( int fd ) {   struct irda_device_list *list; unsigned short int daddr = -1; char *buf; socklen_t len; int i;   len = sizeof(struct irda_device_list) + sizeof(struct irda_device_info) * ( DISCOVER_MAX_DEVICES - 1 ); buf = malloc( sizof(char) * len ); if( buf ) { /* Get list of discovered devices */ for ( i=0 ; i &lt; NUM_TRIES ; i++ ) { if ( !getsockopt( fd, SOL_IRLMP, IRLMP_ENUMDEVICES, buf, &amp;len) ) { if (!i &amp;&amp; len == 4) { sleep(1); continue; /* Ignore first empty entry */ }         break; }       if (errno == EAGAIN) sleep(1); else { perror("discover"); break; }     }      list = (struct irda_device_list *) buf; if( len > 4 && list->len > 0 ) /* Find and use the first Smart device discovered */ for (i = 0 ; i < list->len ; i++ ) { if( !strncmp( list->dev[i].info, "Aladin Smart", 12 ) ) { /* discovered, return daddr */ fprintf( stderr, "Discovered: %s\n",                    list->dev[i].info); daddr = list-&gt;dev[i].daddr; }       }      }     free( buf ); }  return daddr; }

gint smart_connect(void) {   int fd, rval; struct sockaddr_irda peer; fd = socket( AF_IRDA, SOCK_STREAM, 0 ); if( fd < 0 ) return -1; /* Invalid socket */ peer.sir_addr = smart_discover( fd ); if( peer.sir_addr == -1 ) return -1; /* discover failed */ peer.sir_family = AF_IRDA; peer.sir_lsap_sel = 1; peer.sir_name[0]='\0'; if( connect( fd, (struct sockaddr *) &amp;peer, sizeof( struct sockaddr_irda ) ) ) { perror( "connect" ); return -1; /* connect failed */ }   fprintf( stderr, "Connected\n" ); return fd; }

Data Format
All data returned from a Smart dive computer is big endian i.e. is returned most significant byte first.

For example, if a double word with the byte sequence 0x01 0x02 0x03 0x04 is returned by the dive computer, it needs to converted to 0x04 0x03 0x02 0x01 to be a valid doble word on a PC.

Downloading
Once the connection is made, the following is done to download data from the dive computer to the PC:

Step 1 - Handshake

write 1 byte value = 0x1b read 1 byte. This must = 0x01 write 5 bytes: 0x1c, 0x10, 0x27, 0, 0 read 1 byte. This must be = 0x01

If either of the returned bytes have a value other than 0x01, then the handshake has failed.

Step 2 - Get the internal time of dive computer

write byte: 0x26 read 4 bytes

This is a big endian double word equal to the number of semiseconds (2 semisecond = 1 seconds) since 2000-01-01 00:00:00. The time of the PC should be taken at the same time this value is retrieved so that the two can be related and the start date and time of individual dives can be calculated.

Example C code:

unsigned int get_reference_time_semiseconds(void) {  /* Returns 2000-01-01 00:00:00 in semiseconds */ struct tm t;  t.tm_sec = 0; t.tm_min = 0; t.tm_hour = 0; t.tm_mday = 1; t.tm_mon = 0; t.tm_year = 100; return mktime(&amp;t) * 2; }

unsigned long get_system_time_semiseconds(void) {  /* returns system time in semiseconds since */ /* 2000-01-01 00:00:00.                    */   struct timeval tv; gettimeofday( &amp;tv, NULL ); return ( tv.tv_sec * 2 ) + ( tv.tv_usec / 500000 ) - get_reference_time_semiseconds; }

time_t calculate_dive_start_date_time_seconds(   unsigned long sys_time_semiseconds,   unsigned long smart_internal_time_semiseconds,   unsigned long smart_dive_time_semiseconds ) {  /* returns the start date and time of a dive as seconds      */ /* since epoc. Adjusting for DST is also needed - see below. */  return ( ( smart_dive_time_semiseconds + sys_time_semiseconds - smart_internal_time_semiseconds )     + get_reference_time_semiseconds ) / 2; }

unsigned long convert_sys_time_seconds_to_smart_time(time_t timer) {  /* Convert given seconds from epoc (timer) to smart time */ /* in semiseconds. */  /* This can be used to tell the smart dive computer when */ /* to down load dives from - see steps 5 &amp; 6. */  return timer * 2 - get_reference_time_semiseconds; }

To adjust for daylight saving, use the following:

IF sys_time is DST AND dive_time is not DST THEN increment dive start time by 1 hour IF sys_time is not DST AND dive_time is DST THEN decrement dive start time by 1 hour

Step 3 - get the serial number of the dive computer

write byte: 0x20 read 4 bytes

This is a big endian double word.

Step 4 - get dive computer type

write byte: 0x16 read byte

The meaning of the read byte is interpreted as such:
 * 0x10 : SmartPRO
 * 0x12 : Aladin Tec or Prime
 * 0x14 : SmartCOM
 * 0x18 : SmartTEC

Step 5 - get the number of byte of data to be returned from dive computer

write 9 bytes: 0xc6, from when (4 bytes), 0x10, 0x27, 0, 0

The "from when (4 bytes)" above are a big endian double word containing the date and time, in semi-seconds from 2000-01-01 00:00:00, from which to start downloading dives. If you want to download all dives they should be filled with zeros. See the section Time.

read 4 bytes

The read bytes are a big endian double word containing the number of bytes that will be returned by the dive computer. See the section Internal Time.

Step 6 - get the data

write 9 bytes: 0xc6, from when (4 bytes), 0x10, 0x27, 0, 0

The "from when (4 bytes)" 4 bytes are the same as step 5.

Read <=32 byte blocks until the required number of bytes (determined in step 5) have been recieved.