Merge "Make stdout be line buffered."
diff --git a/attrib/gattrib.c b/attrib/gattrib.c
index 912dffb..c388625 100644
--- a/attrib/gattrib.c
+++ b/attrib/gattrib.c
@@ -156,7 +156,8 @@
 
 	refs = __sync_add_and_fetch(&attrib->refs, 1);
 
-	DBG("%p: ref=%d", attrib, refs);
+	// suppress very common debug message
+	if (refs == 1 || refs > 20) DBG("%p: ref=%d", attrib, refs);
 
 	return attrib;
 }
@@ -230,7 +231,8 @@
 
 	refs = __sync_sub_and_fetch(&attrib->refs, 1);
 
-	DBG("%p: ref=%d", attrib, refs);
+	// suppress very common debug message
+	if (refs == 0 || refs > 20) DBG("%p: ref=%d", attrib, refs);
 
 	if (refs > 0)
 		return;
diff --git a/profiles/battery/battery.c b/profiles/battery/battery.c
index 1bbaa48..58be9fd 100644
--- a/profiles/battery/battery.c
+++ b/profiles/battery/battery.c
@@ -37,6 +37,7 @@
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/param.h>
 #include <fcntl.h>
 
 #include <bluetooth/bluetooth.h>
@@ -61,8 +62,9 @@
 #define BATT_UUID		"0000180f-0000-1000-8000-00805f9b34fb"
 #define BATT_LEVEL_UUID		"00002a19-0000-1000-8000-00805f9b34fb"
 
-//#define CHECK_TIME		(24*60*60)
-#define CHECK_TIME		10
+#define BATTERY_DIR		"/tmp/batteries"
+
+#define CHECK_TIME		(8*60*60)
 
 struct service {
 	guint			attio_id;
@@ -79,7 +81,9 @@
 
 	struct service		level;
 
-	time_t			lastCheck;
+	int			batt_level;
+
+	time_t			next_check;
 };
 
 static GSList *devices = NULL;
@@ -89,7 +93,6 @@
 {
 	struct batt_device *battdev;
 
-	DBG("BATT trace");
 	battdev = g_try_new0(struct batt_device, 1);
 	if (!battdev)
 		return NULL;
@@ -102,7 +105,6 @@
 
 static void batt_free_device(struct batt_device *battdev)
 {
-	DBG("BATT trace");
 	btd_device_unref(battdev->device);
 	g_attrib_unref(battdev->attrib);
 	g_free(battdev->batt_primary);
@@ -119,23 +121,67 @@
 	return ts.tv_sec;
 }
 
+/* should really be done in the dbus agent, I think */
+static void batt_update_level_file(struct batt_device* battdev)
+{
+	char path[MAXPATHLEN], pathnew[MAXPATHLEN];
+
+	const char* addr = NULL;
+	const char* id = device_get_path(battdev->device);
+
+	/* id == /org/bluez/hci0/dev_D0_5F_B8_29_10_8D */
+
+	if (id != NULL) {
+		addr = rindex(id, '/');
+		if (addr == NULL) {
+			addr = id;
+		} else {
+			addr++;
+		}
+	}
+	if (addr == NULL || *addr == '\0') {
+		addr = "UNKNOWN";
+	}
+
+	snprintf(path, sizeof path, "%s/%s", BATTERY_DIR, addr);
+	snprintf(pathnew, sizeof pathnew, "%s.new", path);
+
+	if (mkdir(BATTERY_DIR, 0777) < 0 && errno != EEXIST) {
+		error("BATT mkdir: %s: %s", BATTERY_DIR, strerror(errno));
+		return;
+	}
+
+	FILE* fp = fopen(pathnew, "w");
+	if (fp == NULL) {
+		error("BATT fopen: %s: %s", pathnew, strerror(errno));
+		return;
+	}
+	fprintf(fp, "%d\n", battdev->batt_level);
+	fclose(fp);
+
+	if (rename(pathnew, path) != 0) {
+		error("BATT rename: %s -> %s: %s", pathnew, path, strerror(errno));
+		return;
+	}
+}
+
 static void level_read_char_cb(guint8 status, const guint8 *pdu, guint16 len,
 							gpointer user_data)
 {
 	struct batt_device *battdev = user_data;
 
-	DBG("BATT trace");
 	if (status != 0) {
 		error("BATT %s failed: %s", __func__, att_ecode2str(status));
 		return;
 	}
-	int level = pdu[1];
-	DBG("BATT level=%d", level);
+	battdev->batt_level = pdu[1];
+	DBG("BATT level=%d", battdev->batt_level);
+	batt_update_level_file(battdev);
 }
 
-static void checkLevel(struct batt_device *battdev)
+static void check_level(struct batt_device *battdev)
 {
-	battdev->lastCheck = wallclock();
+	battdev->next_check = wallclock() + CHECK_TIME;
 	if (battdev->level.value_handle == 0)
 		return;
 
@@ -143,13 +189,11 @@
 						level_read_char_cb, battdev);
 }
 
-static int isTimeForLevelCheck(struct batt_device* battdev)
+static int is_time_for_level_check(struct batt_device* battdev)
 {
-	DBG("BATT trace");
 
 	time_t now = wallclock();
-	time_t dt = now - battdev->lastCheck;
-	if (dt < CHECK_TIME) {
+	if (now < battdev->next_check) {
 		DBG("BATT not time for level check yet");
 		return 0;
 	}
@@ -190,8 +234,8 @@
 			battdev->level.value_handle = chr->value_handle;
 			DBG("BATT found BATT_LEVEL_UUID value_handle=0x%04x", chr->value_handle);
 			//discover_desc(battdev, chr, next);
-			if (isTimeForLevelCheck(battdev)) {
-				checkLevel(battdev);
+			if (is_time_for_level_check(battdev)) {
+				check_level(battdev);
 			}
 		}
 	}
@@ -203,7 +247,6 @@
 	struct gatt_primary *prim = battdev->batt_primary;
 	GSList *l;
 
-	DBG("BATT trace");
 	DBG("BATT connected");
 
 	battdev->attrib = g_attrib_ref(attrib);
@@ -216,8 +259,8 @@
 						prim->range.end, NULL,
 						char_discovered_cb, battdev);
 	} else {
-		if (isTimeForLevelCheck(battdev)) {
-			checkLevel(battdev);
+		if (is_time_for_level_check(battdev)) {
+			check_level(battdev);
 		}
 	}
 }
@@ -227,7 +270,6 @@
 	struct batt_device *battdev = user_data;
 	GSList *l;
 
-	DBG("BATT trace");
 	DBG("BATT disconnected");
 
 	if (battdev->level.attio_id > 0) {
@@ -246,7 +288,6 @@
 	GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
 	GIOChannel *io;
 
-	DBG("BATT trace");
 	battdev = batt_new_device(device, prim->range.start);
 	if (!battdev)
 		return NULL;
@@ -263,7 +304,6 @@
 
 static int batt_unregister_device(struct batt_device *battdev)
 {
-	DBG("BATT trace");
 	btd_device_remove_attio_callback(battdev->device, battdev->attioid);
 	batt_free_device(battdev);
 
@@ -276,7 +316,6 @@
 	const char *path = device_get_path(device);
 	GSList *primaries, *l;
 
-	DBG("BATT trace");
 	DBG("BATT path %s", path);
 
 	primaries = btd_device_get_primaries(device);
@@ -307,7 +346,6 @@
 	struct batt_device *battdev = a;
 	struct btd_device *device = b;
 
-	DBG("BATT trace");
 	if (battdev->device != device)
 		return;
 
@@ -320,7 +358,6 @@
 	struct btd_device *device = btd_service_get_device(service);
 	const char *path = device_get_path(device);
 
-	DBG("BATT trace");
 	DBG("BATT path %s", path);
 
 	g_slist_foreach(devices, remove_device, device);
@@ -337,13 +374,11 @@
 {
 	int err;
 
-	DBG("BATT trace");
 	return btd_profile_register(&batt_profile);
 }
 
 static void batt_exit(void)
 {
-	DBG("BATT trace");
 	btd_profile_unregister(&batt_profile);
 }
 
diff --git a/profiles/oad/oad.c b/profiles/oad/oad.c
index 2936b6a..fd37cd7 100644
--- a/profiles/oad/oad.c
+++ b/profiles/oad/oad.c
@@ -37,6 +37,7 @@
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/param.h>
 #include <fcntl.h>
 
 #include <bluetooth/bluetooth.h>
@@ -62,25 +63,31 @@
 #define OAD_IMG_IDENTITY_UUID	"f000ffc1-0451-4000-b000-000000000000"
 #define OAD_IMG_BLOCK_UUID	"f000ffc2-0451-4000-b000-000000000000"
 
-#define MAJOR_VERSION(x)	((x) >> 8)
-#define MINOR_VERSION(x)	((x) & 0xff)
-#define SLOT_VERSION(x)		(!(MINOR_VERSION(x) & 0x1))
+#define OAD_MAJOR_VERSION(x)	((x) >> 8)
+#define OAD_MINOR_VERSION(x)	((x) & 0xff)
 
-#define FIRMWARE_SLOT0		"/tmp/gfrm200.slot0.bin"
-#define FIRMWARE_SLOT1		"/tmp/gfrm200.slot1.bin"
+/* remote has 2 processors, 2 firmwares. */
+#define OAD_GP_FIRMWARE		"gfrm200.gp.bin"
+#define OAD_TI_FIRMWARE		"gfrm200.ti.bin"
 
-//#define CHECK_TIME		(24*60*60)
-#define CHECK_TIME		10
+/* remote checks in every 15 minutes */
+#define OAD_UPGRADE_DELAY_SECS		10		// delay between wake and upgrade check
+#define OAD_DELAY_NO_UPGRADE		(8*60*60)	// check for new ACS files (not likely)
+#define OAD_DELAY_UPGRADING		60		// recheck in 1 minute (in case of 2 firmware update)
+
+#define OAD_HEADER_LEN			8		// on-disk header.  See OAD_Formatv7.xlsx
 
 enum OAD_Status {
 	OADSTATUS_None            = 0,
 	OADSTATUS_DataTransfer    = 1,
 	OADSTATUS_TransferSuccess = 2,
+	OADSTATUS_Unknown81       = 0x81,	// maybe flashing
+	OADSTATUS_Unknown82       = 0x82,	// maybe rebooting
 	OADSTATUS_CRCError        = 0xE0,
 	OADSTATUS_LVD             = 0xE1,	// low voltage detection
 	OADSTATUS_KeyPress        = 0xE2,	// user interrupted (not idle)
 	OADSTATUS_GP              = 0xE3,	// bootloader said no
-	OADSTATUS_Host            = 0xE4,	// host did something wrong (eg, timeout)
+	OADSTATUS_Host            = 0xE4,	// host error (eg, timeout)
 };
 
 enum OAD_Action {
@@ -89,15 +96,21 @@
 	OADACTION_StartUpdate     = 2,
 };
 
-struct fwinfo {
-	uint16_t	gp_version;
-	uint16_t	ti_version;
-	uint32_t	gp_size;
-	uint32_t	ti_size;
-	int		slot;
+enum OAD_FirmwareType {
+	OADFW_Unknown = -1,
+	OADFW_GP = 0,
+	OADFW_TI = 1,
+	OADFW_Total = 2,
 };
 
-struct service {
+struct oad_fwinfo {
+	uint16_t		version;
+	uint32_t		size;
+	uint8_t			header[OAD_HEADER_LEN];
+	char			file[MAXPATHLEN];
+};
+
+struct oad_service {
 	guint			attio_id;
 	uint16_t		ccc_handle;
 	uint16_t		value_handle;
@@ -111,35 +124,34 @@
 	guint			attioid;
 	struct gatt_primary	*oad_primary;
 
-	struct service		identity;
-	struct service		block;
+	struct oad_service	identity;
+	struct oad_service	block;
 
-	time_t			lastCheck;
-	struct fwinfo		remote;
+	time_t			nextCheck;
+	struct oad_fwinfo	disk[OADFW_Total];
+	struct oad_fwinfo	remote[OADFW_Total];
 
-	uint8_t			header[8];
+	int			force_upgrade;
+	int			retries;
+	int			num_blocks;
+	int			progress;
+
+	uint8_t			header[OAD_HEADER_LEN];
 	int			fd;
-	guint			tid;		/* Timer id to delay starting upgrade */
+	guint			tid;			// Timer id to delay starting upgrade
 };
 
-struct characteristic {
+struct oad_characteristic {
 	struct oad_device	*oaddev;
 	char			uuid[MAX_LEN_UUID_STR + 1];
 };
 
-static GSList *devices = NULL;
+static GSList *oad_devices = NULL;
 
-//#define FAKEFW	1
-#ifdef FAKEFW
-static unsigned char HEADER[] = { 0xaa, 0x55, 0x01, 0x02, 0x0d, 0x04, 0x00, 0x54, 0, 0, 0, 0, 0, 0, 0, 0, };
-#endif
-
-static struct oad_device *oad_new_device(struct btd_device *device,
-								uint16_t id)
+static struct oad_device *oad_new_device(struct btd_device *device, uint16_t id)
 {
 	struct oad_device *oaddev;
 
-	DBG("OAD trace");
 	oaddev = g_try_new0(struct oad_device, 1);
 	if (!oaddev)
 		return NULL;
@@ -153,7 +165,6 @@
 
 static void oad_free_device(struct oad_device *oaddev)
 {
-	DBG("OAD trace");
 	if (oaddev->fd != -1) {
 		close(oaddev->fd);
 		oaddev->fd = -1;
@@ -164,36 +175,43 @@
 	g_free(oaddev);
 }
 
-static void block_char_write_cb(guint8 status, const guint8 *pdu, guint16 len,
-							gpointer user_data)
+static void oad_block_char_write_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
 {
 	struct oad_device *oaddev = user_data;
 
-	DBG("OAD trace");
+	if (status != 0) {
+		error("OAD %s failed: %s", __func__, att_ecode2str(status));
+		return;
+	}
+	oaddev->retries = 0;	// successful write
+}
+
+static void oad_identity_char_write_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
+{
+	struct oad_device *oaddev = user_data;
+
 	if (status != 0) {
 		error("OAD %s failed: %s", __func__, att_ecode2str(status));
 		return;
 	}
 }
 
-static void identity_char_write_cb(guint8 status, const guint8 *pdu, guint16 len,
-							gpointer user_data)
+static void oad_start_transfer(struct oad_device* oaddev)
 {
-	struct oad_device *oaddev = user_data;
+	uint8_t attr_val[OAD_HEADER_LEN+1];		// + 1 byte action
 
-	DBG("OAD trace");
-	if (status != 0) {
-		error("OAD %s failed: %s", __func__, att_ecode2str(status));
-		return;
-	}
+	memcpy(attr_val, oaddev->header, sizeof oaddev->header);
+	attr_val[OAD_HEADER_LEN] = OADACTION_TransferOADData;	/* start OAD image transfer */
+
+	gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+			sizeof(attr_val), oad_identity_char_write_cb, oaddev);
 }
 
-static void block_notify_handler(const uint8_t *pdu, uint16_t len,
-							gpointer user_data)
+static void oad_block_notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
 {
 	struct oad_device *oaddev = user_data;
+	int retry = 0;
 
-	DBG("OAD trace");
 
 	/* should be at least opcode (1b) + handle (7a00) */
 	if (len < 3) {
@@ -215,32 +233,27 @@
 	case OADSTATUS_DataTransfer:		// send a block
 		{
 			uint8_t block[18];	// 2 bytes block_num + 16 data bytes
+
 			memset(block, 0, sizeof block);
 			put_le16(block_num, block);
 
 			if (lseek(oaddev->fd, 16 * block_num, SEEK_SET) < 0) {
-				perror("OAD seek");
-				close(oaddev->fd);
-				oaddev->fd = -1;
-				return;
+				perror("OAD firmware seek");
+				goto error;
 			}
-			if (read(oaddev->fd, block+2, sizeof block - 2) != sizeof block - 2) {
-				perror("OAD short read");
-				close(oaddev->fd);
-				oaddev->fd = -1;
-				return;
+			if (read(oaddev->fd, block+2, sizeof block - 2) < 0) {
+				perror("OAD firmware read");
+				goto error;
 			}
-#ifdef FAKEFW
-if (block_num == 0) {
-	memcpy(block+2, HEADER, sizeof block - 2);
-} else {
-	memset(block+2, 0, sizeof block - 2);
-}
-#endif
 
-			DBG("OAD sending block %d", block_num);
+			int progress = 0;
+			if (oaddev->num_blocks > 0) progress = 100 * block_num / oaddev->num_blocks;
+			if (oaddev->progress != progress) {
+				oaddev->progress = progress;
+				DBG("OAD sending block %d (%d%%)", block_num, progress);
+			}
 			gatt_write_char(oaddev->attrib, oaddev->block.value_handle, block,
-					sizeof(block), block_char_write_cb, oaddev);
+				sizeof(block), oad_block_char_write_cb, oaddev);
 		}
 		break;
 	case OADSTATUS_TransferSuccess:		// OK, successful transfer
@@ -254,27 +267,48 @@
 			attr_val[8] = OADACTION_StartUpdate;		/* commit fw image */
 			DBG("OAD committing firmware");
 			gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
-						sizeof(attr_val), identity_char_write_cb, oaddev);
+				sizeof(attr_val), oad_identity_char_write_cb, oaddev);
 		}
 		break;
-	case OADSTATUS_CRCError:	// checksum error
-		DBG("OAD firmware transfer checksum error");
-		close(oaddev->fd);
-		oaddev->fd = -1;
+	case OADSTATUS_Unknown81:
+	case OADSTATUS_Unknown82:
+		DBG("OAD block notify status 0x%02x (might be a good thing)", status);
 		break;
-	case OADSTATUS_LVD:	// ???
-	case OADSTATUS_KeyPress:	// ???
-	case OADSTATUS_GP:	// ???
-	case OADSTATUS_Host:	// ???
+
+	case OADSTATUS_CRCError:
+		DBG("OAD firmware transfer checksum error");
+		goto error;
+	case OADSTATUS_LVD:
+		DBG("OAD firmware upgrade low battery error");
+		goto error;
+	case OADSTATUS_KeyPress:
+		DBG("OAD firmware upgrade user interrupted");
+		goto error;
+	case OADSTATUS_GP:
+		DBG("OAD firmware upgrade GP error");
+		goto error;
+	case OADSTATUS_Host:
+		DBG("OAD firmware upgrade host error (eg, timeout)");
+		if (oaddev->retries++ < 3) {
+			DBG("OAD retrying upgrade...", status);
+			oad_start_transfer(oaddev);
+			return;
+		}
+		goto error;
 	default:
 		DBG("OAD unexpected block notify status 0x%02x", status);
-		close(oaddev->fd);
-		oaddev->fd = -1;
-		break;
+		goto error;
 	}
+	/* success so far*/
+	return;
+
+error:
+	DBG("OAD upgrade stopping due to error", status);
+	close(oaddev->fd);
+	oaddev->fd = -1;
 }
 
-static time_t wallclock(void)
+static time_t oad_wallclock(void)
 {
 	struct timespec ts;
 
@@ -284,14 +318,11 @@
 	return ts.tv_sec;
 }
 
-static int isTimeForUpgradeCheck(struct oad_device* oaddev)
+static int oad_is_time_for_upgrade_check(struct oad_device* oaddev)
 {
-	DBG("OAD trace");
+	time_t now = oad_wallclock();
 
-	time_t now = wallclock();
-
-	time_t dt = now - oaddev->lastCheck;
-	if (dt < CHECK_TIME) {
+	if (now < oaddev->nextCheck) {
 		DBG("OAD not time for upgrade check yet");
 		return 0;
 	}
@@ -299,96 +330,162 @@
 	return 1;
 }
 
-static void checkForUpgrade(struct oad_device *oaddev)
+static void oad_get_firmware_info(struct oad_device *oaddev, const char* file)
 {
-	DBG("OAD trace");
+	uint8_t	header[OAD_HEADER_LEN];
 
-	if (!isTimeForUpgradeCheck(oaddev)) {
-		return;
-	}
-
-	oaddev->lastCheck = wallclock();
-
-	int slot2 = !oaddev->remote.slot;	// alternate between slot 0 and 1
-
-	char* fw_file = slot2 ? FIRMWARE_SLOT1 : FIRMWARE_SLOT0;
-	int fd = open(fw_file, O_RDONLY);
+	int fd = open(file, O_RDONLY);
 	if (fd < 0) {
-		error("OAD open: %s: %s", fw_file, strerror(errno));
+		error("OAD open: %s: %s", file, strerror(errno));
 		return;
 	}
-	int len = read(fd, oaddev->header, sizeof (oaddev->header));
-#ifdef FAKEFW
-memcpy(oaddev->header, HEADER, sizeof oaddev->header);
-#endif
+	int len = read(fd, header, sizeof header);
 	if (len < 0) {
-		error("OAD read: %s: %s", fw_file, strerror(errno));
+		error("OAD read: %s: %s", file, strerror(errno));
 		close(fd);
 		return;
 	}
-
-	if (len < sizeof oaddev->header) {
-		error("OAD short read: %s: wanted %d, got %d", fw_file, sizeof oaddev->header, len);
+	if (len < sizeof header) {
+		error("OAD short read: %s: wanted %d, got %d", file, sizeof header, len);
 		close(fd);
 		return;
 	}
+	close(fd);
+	fd = -1;
 
-	/* 8 bytes of header appear to be:
+	/* See OAD_Formatv7.xlsx, 'Image Info' tab.  8 bytes are:
 	 * 2 bytes checksum
 	 * 2 bytes version
 	 * 3 bytes size
-	 * 1 byte type: "T" TI image, "G" GP image
+	 * 1 byte id: "T" TI image, "G" GP image
 	 */
-	uint16_t version = get_le16(oaddev->header + 2);
-	int type = oaddev->header[7];
-	if (type == 'T') {
-		int slot = SLOT_VERSION(version);
-		DBG("OAD firmware on disk: file=%s ti_version=%d.%d slot=%d",
-			fw_file, MAJOR_VERSION(version), MINOR_VERSION(version), slot);
+	uint16_t version = get_le16(header + 2);
+	uint32_t size = get_le32(header+4) & 0x00ffffff;
+	int id = header[7];
+	enum OAD_FirmwareType type = id == 'T' ? OADFW_TI : id == 'G' ? OADFW_GP : OADFW_Unknown;
+	int index = (int)type;
 
-		if (version <= oaddev->remote.ti_version) {
-			DBG("OAD TI firmware is up to date");
-			close(fd);
-			return;
-		}
-	} else if (type == 'G') {
-		DBG("OAD firmware on disk: file=%s gp_version=%d.%d",
-			fw_file, MAJOR_VERSION(version), MINOR_VERSION(version));
-		if (version <= oaddev->remote.gp_version) {
-			DBG("OAD GP firmware is up to date");
-			close(fd);
-			return;
-		}
-	} else {
-		error("OAD firmware on disk: unknown type '%c': file=%s version=%d.%d",
-			type, fw_file, MAJOR_VERSION(version), MINOR_VERSION(version));
-		close(fd);
+	if (type == OADFW_Unknown) {
+		error("OAD unknown firmware id '%c': file=%s version=%d.%d",
+			id, file, OAD_MAJOR_VERSION(version), OAD_MINOR_VERSION(version));
+		return;
+	}
+	if (version <= oaddev->disk[index].version) {
+		error("OAD ignoring older firmware: file=%s type=%s version=%d.%d",
+			file, type, OAD_MAJOR_VERSION(version), OAD_MINOR_VERSION(version));
+		return;
+	}
+	if (strlen(file) >= sizeof oaddev->disk[index].file) {
+		error("OAD firmware file name too long: %s", file);
 		return;
 	}
 
-	DBG("OAD starting upgrade");
+	oaddev->disk[index].version = version;
+	oaddev->disk[index].size = size;
+	memcpy(oaddev->disk[index].header, header, sizeof oaddev->disk[index].header);
+	strncpy(oaddev->disk[index].file, file, sizeof oaddev->disk[index].file);
+	oaddev->disk[index].file[sizeof oaddev->disk[index].file - 1] = '\0';
+}
+
+static const char* oad_firmware_type_string(enum OAD_FirmwareType type)
+{
+	switch (type) {
+	case OADFW_GP:	return "GP";
+	case OADFW_TI:	return "TI";
+	}
+	return "Unknown";
+}
+
+static void oad_find_firmware(struct oad_device *oaddev)
+{
+	memset(oaddev->disk, 0, sizeof oaddev->disk);
+
+	/* check in tmp first, to make debugging easier */
+	if (access("/tmp/" OAD_GP_FIRMWARE, R_OK) == 0 ||
+	    access("/tmp/" OAD_TI_FIRMWARE, R_OK) == 0) {
+		DBG("OAD found firmware in /tmp, ignoring /etc/remote, enabling downgrade.");
+		oaddev->force_upgrade = 1;	// allow downgrade
+		oad_get_firmware_info(oaddev, "/tmp/" OAD_GP_FIRMWARE);
+		oad_get_firmware_info(oaddev, "/tmp/" OAD_TI_FIRMWARE);
+	} else {
+		oaddev->force_upgrade = 0;
+		oad_get_firmware_info(oaddev, "/etc/remote/" OAD_GP_FIRMWARE);
+		oad_get_firmware_info(oaddev, "/etc/remote/" OAD_TI_FIRMWARE);
+	}
+
+	int i;
+	for (i = 0; i < OADFW_Total; i++) {
+		enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
+		const char* typeStr = oad_firmware_type_string(type);
+		if (strlen(oaddev->disk[type].file) == 0) {
+			error("OAD no %s firmware found on disk", typeStr);
+		} else {
+			DBG("OAD found %s firmware: file=%s version=%d.%d",
+				typeStr, oaddev->disk[i].file,
+				OAD_MAJOR_VERSION(oaddev->disk[i].version),
+				OAD_MINOR_VERSION(oaddev->disk[i].version));
+		}
+	}
+}
+
+static void oad_check_for_upgrade(struct oad_device *oaddev)
+{
+	if (!oad_is_time_for_upgrade_check(oaddev)) {
+		return;
+	}
+
+	oad_find_firmware(oaddev);
+
+	struct oad_fwinfo* fp = NULL;
+	int i;
+	for (i = 0; i < OADFW_Total; i++) {
+		enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
+		const char* typeStr = oad_firmware_type_string(type);
+		if (strlen(oaddev->disk[i].file) == 0) {
+			continue;
+		}
+		if (oaddev->disk[i].version == oaddev->remote[i].version ||
+		    (!oaddev->force_upgrade &&
+		     oaddev->disk[i].version < oaddev->remote[i].version)) {
+			DBG("OAD %s firmware is up to date", typeStr);
+		} else {
+			fp = &oaddev->disk[i];
+			break;
+		}
+	}
+
+	if (fp == NULL) {
+		oaddev->nextCheck = oad_wallclock() + OAD_DELAY_NO_UPGRADE;
+		return;
+	}
+	oaddev->nextCheck = oad_wallclock() + OAD_DELAY_UPGRADING;
+
+	DBG("OAD starting upgrade with %s", fp->file);
+
 	if (oaddev->fd != -1) {
 		close(oaddev->fd);
 	}
-	oaddev->fd = fd;	/* fd is left open */
+	oaddev->fd = open(fp->file, O_RDONLY);
+	if (oaddev->fd < 0) {
+		error("OAD open: %s: %s", fp->file, strerror(errno));
+		return;
+	}
 	if (oaddev->tid) {
 		g_source_remove(oaddev->tid);
+		oaddev->tid = 0;
 	}
 
-	uint8_t attr_val[9];
-	memcpy(attr_val, oaddev->header, sizeof oaddev->header);
-	attr_val[8] = OADACTION_TransferOADData;	/* start OAD image transfer */
-
-	gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
-			sizeof(attr_val), identity_char_write_cb, oaddev);
+	oaddev->progress = 0;
+	oaddev->retries = 0;
+	oaddev->num_blocks = (fp->size + 15) / 16;
+	memcpy(oaddev->header, fp->header, sizeof oaddev->header);
+	oad_start_transfer(oaddev);
 }
 
-static void identity_notify_handler(const uint8_t *pdu, uint16_t len,
-							gpointer user_data)
+static void oad_identity_notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
 {
 	struct oad_device *oaddev = user_data;
 
-	DBG("OAD trace");
 
 	/* should be at least opcode (1b) + handle (76 00) */
 	if (len < 3) {
@@ -396,7 +493,7 @@
 		return;
 	}
 
-	/* Next 10 bytes appear to be:
+	/* See OAD_Formatv7.xlsx, 'Image Info' tab.  10 bytes are:
 	 *	char gp_version[2];	// 09 00 is 0.9
 	 * 	char gp_size[3];	// 00 00 01 is 0x010000 == 64k
 	 *	char ti_version[2];	// 2a 00 is 0.42
@@ -408,31 +505,30 @@
 		return;
 	}
 
-	oaddev->remote.gp_version = get_le16(pdu+3);
-	oaddev->remote.gp_size = get_le32(pdu+5) & 0x00ffffff;
-	oaddev->remote.slot = SLOT_VERSION(oaddev->remote.gp_version);
-	oaddev->remote.ti_version = get_le16(pdu+8);
+	oaddev->remote[OADFW_GP].version = get_le16(pdu+3);
+	oaddev->remote[OADFW_GP].size = get_le32(pdu+5) & 0x00ffffff;
+
+	oaddev->remote[OADFW_TI].version = get_le16(pdu+8);
 	unsigned char size[4];
 	memcpy(size, pdu+10, 3);
 	size[3] = 0;				// need 4 bytes for get_le32
-	oaddev->remote.ti_size = get_le32(size);
+	oaddev->remote[OADFW_TI].size = get_le32(size) & 0x00ffffff;
 
-	DBG("OAD firmware on remote: gp_version=%d.%d gp_size=%d ti_version=%d.%d ti_size=%d slot=%d",
-		MAJOR_VERSION(oaddev->remote.gp_version), MINOR_VERSION(oaddev->remote.gp_version),
-		oaddev->remote.gp_size,
-		MAJOR_VERSION(oaddev->remote.ti_version), MINOR_VERSION(oaddev->remote.ti_version),
-		oaddev->remote.ti_size,
-		oaddev->remote.slot);
-
-	checkForUpgrade(oaddev);
+	int i;
+	for (i = 0; i < OADFW_Total; i++) {
+		enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
+		const char* typeStr = oad_firmware_type_string(type);
+		DBG("OAD %s firmware on remote: version=%d.%d",
+			typeStr, OAD_MAJOR_VERSION(oaddev->remote[i].version),
+			OAD_MINOR_VERSION(oaddev->remote[i].version));
+	}
+	oad_check_for_upgrade(oaddev);
 }
 
-static void check_fwversion(struct oad_device *oaddev)
+static void oad_check_fwversion(struct oad_device *oaddev)
 {
 	uint8_t attr_val[9];	// 8 byte header + 1 command byte (all zeros)
 
-	DBG("OAD trace");
-
 	if (!oaddev->identity.notify_enabled || !oaddev->block.notify_enabled) {
 		return;
 	}
@@ -441,42 +537,37 @@
 	memset(attr_val, 0, sizeof attr_val);
 	attr_val[8] = OADACTION_GetRemoteInfo;		/* trigger fw version notify */
 	gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
-						sizeof(attr_val), identity_char_write_cb, oaddev);
+		sizeof(attr_val), oad_identity_char_write_cb, oaddev);
 }
 
-static void ccc_char_write_identity_cb(guint8 status, const guint8 *pdu, guint16 len,
-							gpointer user_data)
+static void oad_ccc_char_write_identity_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
 {
 	struct oad_device *oaddev = user_data;
 
-	DBG("OAD trace");
 	if (status != 0) {
 		error("OAD %s failed: %s", __func__, att_ecode2str(status));
 		return;
 	}
 	oaddev->identity.notify_enabled = 1;
 
-	check_fwversion(oaddev);
+	oad_check_fwversion(oaddev);
 }
 
-static void ccc_char_write_block_cb(guint8 status, const guint8 *pdu, guint16 len,
-							gpointer user_data)
+static void oad_ccc_char_write_block_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
 {
 	struct oad_device *oaddev = user_data;
 
-	DBG("OAD trace");
 	if (status != 0) {
 		error("OAD %s failed: %s", __func__, att_ecode2str(status));
 		return;
 	}
 	oaddev->block.notify_enabled = 1;
 
-	check_fwversion(oaddev);
+	oad_check_fwversion(oaddev);
 }
 
-static void enableNotify(struct oad_device* oaddev)
+static void oad_enable_notify(struct oad_device* oaddev)
 {
-	DBG("OAD trace");
 	if (oaddev->identity.ccc_handle == 0 || oaddev->block.ccc_handle == 0 ||
 	    oaddev->identity.value_handle == 0 || oaddev->block.value_handle == 0) {
 	    	error("OAD discovery not complete, cannot enable notifiers");
@@ -486,23 +577,23 @@
 	oaddev->identity.attio_id =
 		g_attrib_register(oaddev->attrib,
 			ATT_OP_HANDLE_NOTIFY, oaddev->identity.value_handle,
-			identity_notify_handler, oaddev, NULL);
+			oad_identity_notify_handler, oaddev, NULL);
 	oaddev->block.attio_id =
 		g_attrib_register(oaddev->attrib,
 			ATT_OP_HANDLE_NOTIFY, oaddev->block.value_handle,
-			block_notify_handler, oaddev, NULL);
+			oad_block_notify_handler, oaddev, NULL);
 
 	uint8_t attr_val[2];
 	put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, attr_val);
 	DBG("OAD enabling notify for OAD_IMG_IDENTITY_UUID");
 	gatt_write_char(oaddev->attrib, oaddev->identity.ccc_handle, attr_val,
-				sizeof(attr_val), ccc_char_write_identity_cb, oaddev);
+		sizeof(attr_val), oad_ccc_char_write_identity_cb, oaddev);
 	DBG("OAD enabling notify for OAD_IMG_BLOCK_UUID");
 	gatt_write_char(oaddev->attrib, oaddev->block.ccc_handle, attr_val,
-				sizeof(attr_val), ccc_char_write_block_cb, oaddev);
+		sizeof(attr_val), oad_ccc_char_write_block_cb, oaddev);
 }
 
-static gboolean upgrade_timer(gpointer data)
+static gboolean oad_upgrade_timer(gpointer data)
 {
 	struct oad_device *oaddev = data;
 
@@ -510,16 +601,15 @@
 	oaddev->tid = 0;
 
 	DBG("OAD timer fired");
-	enableNotify(oaddev);
+	oad_enable_notify(oaddev);
 }
 
-static void desc_discovered_cb(uint8_t status, GSList *descs, void* user_data)
+static void oad_desc_discovered_cb(uint8_t status, GSList *descs, void* user_data)
 {
-	struct characteristic *ch = user_data;
+	struct oad_characteristic *ch = user_data;
 	struct oad_device *oaddev = ch->oaddev;
 	struct GSList *list = NULL;
 
-	DBG("OAD trace");
 	if (status != 0) {
 		error("OAD %s failed: %s", __func__, att_ecode2str(status));
 		goto done;
@@ -541,20 +631,18 @@
 		}
 	}
 	if (oaddev->identity.ccc_handle && oaddev->block.ccc_handle)
-		oaddev->tid = g_timeout_add_seconds(10, upgrade_timer, oaddev);
+		oaddev->tid = g_timeout_add_seconds(OAD_UPGRADE_DELAY_SECS, oad_upgrade_timer, oaddev);
 
 done:
 	g_free(ch);
 }
 
-static void discover_desc(struct oad_device *oaddev, struct gatt_char *c,
-						struct gatt_char *c_next)
+static void oad_discover_desc(struct oad_device *oaddev, struct gatt_char *c, struct gatt_char *c_next)
 {
 	struct gatt_primary *prim = oaddev->oad_primary;
-	struct characteristic *ch;
+	struct oad_characteristic *ch;
 	uint16_t start, end;
 
-	DBG("OAD trace");
 	start = c->value_handle + 1;
 
 	if (c_next != NULL) {
@@ -567,16 +655,16 @@
 		return;
 	}
 
-	ch = g_new0(struct characteristic, 1);
+	ch = g_new0(struct oad_characteristic, 1);
 	ch->oaddev = oaddev;
 	memcpy(ch->uuid, c->uuid, sizeof(c->uuid));
 
 	DBG("OAD discovering descriptors start=0x%04x end=0x%04x", start, end);
 
-	gatt_discover_desc(oaddev->attrib, start, end, NULL, desc_discovered_cb, ch);
+	gatt_discover_desc(oaddev->attrib, start, end, NULL, oad_desc_discovered_cb, ch);
 }
 
-static void char_discovered_cb(uint8_t status, GSList *chars, void* user_data)
+static void oad_char_discovered_cb(uint8_t status, GSList *chars, void* user_data)
 {
 	struct oad_device *oaddev = user_data;
 	struct gatt_primary *prim = oaddev->oad_primary;
@@ -598,8 +686,7 @@
 		chr = l->data;
 		next = l->next ? l->next->data : NULL;
 
-		DBG("OAD 0x%04x UUID: %s properties: %02x",
-				chr->handle, chr->uuid, chr->properties);
+		DBG("OAD 0x%04x UUID: %s properties: %02x", chr->handle, chr->uuid, chr->properties);
 
 
 		start = chr->value_handle + 1;
@@ -608,23 +695,22 @@
 		if (g_strcmp0(chr->uuid, OAD_IMG_IDENTITY_UUID) == 0) {
 			oaddev->identity.value_handle = chr->value_handle;
 			DBG("OAD found OAD_IMG_IDENTITY_UUID value_handle=0x%04x", chr->value_handle);
-			discover_desc(oaddev, chr, next);
+			oad_discover_desc(oaddev, chr, next);
 		} else if (g_strcmp0(chr->uuid, OAD_IMG_BLOCK_UUID) == 0) {
 			oaddev->block.value_handle = chr->value_handle;
 			DBG("OAD found OAD_IMG_BLOCK_UUID value_handle=0x%04x", chr->value_handle);
-			discover_desc(oaddev, chr, next);
+			oad_discover_desc(oaddev, chr, next);
 		}
 
 	}
 }
 
-static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
+static void oad_attio_connected_cb(GAttrib *attrib, gpointer user_data)
 {
 	struct oad_device *oaddev = user_data;
 	struct gatt_primary *prim = oaddev->oad_primary;
 	GSList *l;
 
-	DBG("OAD trace");
 	DBG("OAD connected");
 
 	oaddev->attrib = g_attrib_ref(attrib);
@@ -635,22 +721,20 @@
 	    oaddev->identity.value_handle == 0 || oaddev->block.value_handle == 0) {
 		DBG("OAD discovering characteristics");
 
-		gatt_discover_char(oaddev->attrib, prim->range.start,
-						prim->range.end, NULL,
-						char_discovered_cb, oaddev);
+		gatt_discover_char(oaddev->attrib, prim->range.start, prim->range.end, NULL,
+			oad_char_discovered_cb, oaddev);
 	} else {
-		if (isTimeForUpgradeCheck(oaddev)) {
-			enableNotify(oaddev);
+		if (oad_is_time_for_upgrade_check(oaddev)) {
+			oad_enable_notify(oaddev);
 		}
 	}
 }
 
-static void attio_disconnected_cb(gpointer user_data)
+static void oad_attio_disconnected_cb(gpointer user_data)
 {
 	struct oad_device *oaddev = user_data;
 	GSList *l;
 
-	DBG("OAD trace");
 	DBG("OAD disconnected");
 
 	if (oaddev->identity.attio_id > 0) {
@@ -666,14 +750,12 @@
 	oaddev->attrib = NULL;
 }
 
-static struct oad_device *oad_register_device(struct btd_device *device,
-						struct gatt_primary *prim)
+static struct oad_device *oad_register_device(struct btd_device *device, struct gatt_primary *prim)
 {
 	struct oad_device *oaddev;
 	GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
 	GIOChannel *io;
 
-	DBG("OAD trace");
 	oaddev = oad_new_device(device, prim->range.start);
 	if (!oaddev)
 		return NULL;
@@ -681,16 +763,13 @@
 	oaddev->oad_primary = g_memdup(prim, sizeof(*prim));
 
 	oaddev->attioid = btd_device_add_attio_callback(device,
-							attio_connected_cb,
-							attio_disconnected_cb,
-							oaddev);
+		oad_attio_connected_cb, oad_attio_disconnected_cb, oaddev);
 
 	return oaddev;
 }
 
 static int oad_unregister_device(struct oad_device *oaddev)
 {
-	DBG("OAD trace");
 	btd_device_remove_attio_callback(oaddev->device, oaddev->attioid);
 	oad_free_device(oaddev);
 
@@ -703,7 +782,6 @@
 	const char *path = device_get_path(device);
 	GSList *primaries, *l;
 
-	DBG("OAD trace");
 	DBG("OAD path %s", path);
 
 	primaries = btd_device_get_primaries(device);
@@ -723,22 +801,21 @@
 		if (oaddev == NULL)
 			continue;
 
-		devices = g_slist_append(devices, oaddev);
+		oad_devices = g_slist_append(oad_devices, oaddev);
 	}
 
 	return 0;
 }
 
-static void remove_device(gpointer a, gpointer b)
+static void oad_remove_device(gpointer a, gpointer b)
 {
 	struct oad_device *oaddev = a;
 	struct btd_device *device = b;
 
-	DBG("OAD trace");
 	if (oaddev->device != device)
 		return;
 
-	devices = g_slist_remove(devices, oaddev);
+	oad_devices = g_slist_remove(oad_devices, oaddev);
 	oad_unregister_device(oaddev);
 }
 
@@ -747,10 +824,9 @@
 	struct btd_device *device = btd_service_get_device(service);
 	const char *path = device_get_path(device);
 
-	DBG("OAD trace");
 	DBG("OAD path %s", path);
 
-	g_slist_foreach(devices, remove_device, device);
+	g_slist_foreach(oad_devices, oad_remove_device, device);
 }
 
 static struct btd_profile oad_profile = {
@@ -764,15 +840,12 @@
 {
 	int err;
 
-	DBG("OAD trace");
 	return btd_profile_register(&oad_profile);
 }
 
 static void oad_exit(void)
 {
-	DBG("OAD trace");
 	btd_profile_unregister(&oad_profile);
 }
 
-BLUETOOTH_PLUGIN_DEFINE(oad, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
-							oad_init, oad_exit)
+BLUETOOTH_PLUGIN_DEFINE(oad, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, oad_init, oad_exit)
diff --git a/src/attrib-server.c b/src/attrib-server.c
index e65fff2..810cec5 100644
--- a/src/attrib-server.c
+++ b/src/attrib-server.c
@@ -993,7 +993,8 @@
 	size_t vlen;
 	uint8_t *value = g_attrib_get_buffer(channel->attrib, &vlen);
 
-	DBG("op 0x%02x", ipdu[0]);
+	// suppress very common debug message
+	// DBG("op 0x%02x", ipdu[0]);
 
 	if (len > vlen) {
 		error("Too much data on ATT socket");