diff --git a/apps/ble-demo/src/main.c b/apps/ble-demo/src/main.c
index 2fe1e333a2767173b1416233cffd62d7f0f68897..246f856156fed62e1585f4b244fbc1e31791ba8e 100644
--- a/apps/ble-demo/src/main.c
+++ b/apps/ble-demo/src/main.c
@@ -12,6 +12,15 @@
 
 #include "font.h"
 
+/* TYPES!!! */
+
+struct uv_state {
+	uint16_t period;
+	uint16_t on_duration;
+	uint16_t point_in_cycle;
+	uint16_t intensity;
+};
+
 /* DEFINITIONS!!! */
 
 #define INFORMATION_UUID 0x180A /* "Device Information Service" */
@@ -20,13 +29,11 @@
 
 #define MY_UUID( x ) 0x43, 0x72, 0xE2, 0x22, 0x0D, 0x80, 0x7D, 0x78, \
 	0xE0, 0x43, 0xD5, 0xAB, x & 0xFF, ( x >> 8 ) & 0xFF, 0x38, 0xFA
-#define RW_UUID MY_UUID( 0xAA10 )
-#define READ_UUID MY_UUID( 0xAA11 )
-#define WRITE_UUID MY_UUID( 0xAA12 )
-#define READ_WRITE_UUID MY_UUID( 0xAA13 )
-#define IN_UUID MY_UUID( 0xAA20 )
-#define INDICATE_UUID MY_UUID( 0xAA21 )
-#define NOTIFY_UUID MY_UUID( 0xAA22 )
+#define BANDAGE_UUID MY_UUID( 0xAA20 )
+#define BANDAGE_UV_UUID MY_UUID( 0xAA21 )
+#define BANDAGE_TEMP_UUID MY_UUID( 0xAA22 )
+
+#define MAX_TEMPERATURE_TRANSFER_SIZE 4
 
 /* PROTOTYPES!!! */
 
@@ -43,34 +50,48 @@ static int ble_gap_event( struct ble_gap_event* event, void* data );
 static int ble_info_callback( uint16_t connection_handle,
 	uint16_t attribute_handle, struct ble_gatt_access_ctxt* context,
 	void* data );
-static int ble_rw_callback( uint16_t connection_handle,
+static int ble_uv_callback( uint16_t connection_handle,
 	uint16_t attribute_handle, struct ble_gatt_access_ctxt* context,
 	void* data );
-static int ble_in_callback( uint16_t connection_handle,
+static int ble_temp_callback( uint16_t connection_handle,
 	uint16_t attribute_handle, struct ble_gatt_access_ctxt* context,
 	void* data );
 
 static void gpio_init( void );
 static void button_handler( void* data );
 
-static void timeout( struct os_event* event );
+static void uv_timeout( struct os_event* event );
+static void temperature_timeout( struct os_event* event );
 
 /* GLOBALS!!! */
 
 static struct {
 	uint8_t address_type;
 	uint16_t connection_handle;
-	uint16_t indicate_handle;
-	uint16_t notify_handle;
-	bool indicate_status;
-	bool notify_status;
-	int8_t count;
+	uint16_t uv_handle;
+	uint16_t temp_handle;
+	bool temp_notify_status;
 } ble_state;
 
 uint8_t frame_buffer[128 / 8 * 64 + 1] = { 0x40, 0, };
 uint8_t* frame = &frame_buffer[1];
 const size_t frame_size = 128 / 8 * 64;
-uint32_t t = 0;
+
+static struct {
+	float buffer[2048];
+	size_t buffer_start;
+	size_t buffer_end;
+	struct os_callout callout;
+	uint16_t interval;
+} temperature;
+#define TEMPERATURE_BUFFER_LEN \
+	( sizeof temperature.buffer / sizeof *temperature.buffer )
+
+static struct {
+	struct uv_state state;
+	struct os_callout callout;
+	bool on;
+} uv;
 
 /* DATA!!! */
 
@@ -83,54 +104,84 @@ static const struct ble_gatt_svc_def services[] = {
 				.uuid = BLE_UUID16_DECLARE( MANUFACTURER_UUID ),
 				.access_cb = &ble_info_callback,
 				.flags = BLE_GATT_CHR_F_READ,
+				/* Reading returns a string with the manufacturer
+				 * name */
 			},
 			{
 				.uuid = BLE_UUID16_DECLARE( MODEL_UUID ),
 				.access_cb = &ble_info_callback,
 				.flags = BLE_GATT_CHR_F_READ,
+				/* Reading returns a string with the model number */
 			},
 			{ 0, },
 		},
 	},
 	{
 		.type = BLE_GATT_SVC_TYPE_PRIMARY,
-		.uuid = BLE_UUID128_DECLARE( RW_UUID ),
+		.uuid = BLE_UUID128_DECLARE( BANDAGE_UUID ),
 		.characteristics = (struct ble_gatt_chr_def[]) {
 			{
-				.uuid = BLE_UUID128_DECLARE( READ_UUID ),
-				.access_cb = &ble_rw_callback,
-				.flags = BLE_GATT_CHR_F_READ,
-			},
-			{
-				.uuid = BLE_UUID128_DECLARE( WRITE_UUID ),
-				.access_cb = &ble_rw_callback,
-				.flags = BLE_GATT_CHR_F_WRITE,
-			},
-			{
-				.uuid = BLE_UUID128_DECLARE( READ_WRITE_UUID ),
-				.access_cb = &ble_rw_callback,
+				.uuid = BLE_UUID128_DECLARE( BANDAGE_UV_UUID ),
+				.access_cb = &ble_uv_callback,
+				.val_handle = &( ble_state.uv_handle ),
 				.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE,
-			},
-			{ 0, },
-		},
-	},
-	{
-		.type = BLE_GATT_SVC_TYPE_PRIMARY,
-		.uuid = BLE_UUID128_DECLARE( IN_UUID ),
-		.characteristics = (struct ble_gatt_chr_def[]) {
-			{
-				.uuid = BLE_UUID128_DECLARE( INDICATE_UUID ),
-				.access_cb = &ble_in_callback,
-				.val_handle = &( ble_state.indicate_handle ),
-				.flags = BLE_GATT_CHR_F_INDICATE,
+				/* Both reads and writes have this format:
+				 *
+				 * 	struct __attribute__((packed)) {
+				 * 		uint16_t period;
+				 * 		uint16_t on_duration;
+				 * 		uint16_t point_in_cycle;
+				 * 		uint16_t intensity;
+				 * 	};
+				 *
+				 * 	("__attribute__((packed))" means each struct
+				 * 	elements takes up exactly as much space as it needs.
+				 * 	Otherwise the numbers will sometimes get put 4 bytes
+				 * 	apart even though they're only two bytes long.)
+				 *
+				 * The values period, on duration and point_in_cycle are
+				 * all in seconds.  Intensity is a value between 0 and
+				 * 65535 (way more resolution than we need, but it's
+				 * easier to have everything be the same data type).
+				 * The point_in_cycle value is were we currently are in
+				 * the period, it can be both read and written to.  The
+				 * on duration is at the start of the period. */
 			},
 			{
-				.uuid = BLE_UUID128_DECLARE( NOTIFY_UUID ),
-				.access_cb = &ble_in_callback,
-				.val_handle = &( ble_state.notify_handle ),
-				.flags = BLE_GATT_CHR_F_NOTIFY,
+				.uuid = BLE_UUID128_DECLARE( BANDAGE_TEMP_UUID ),
+				.access_cb = &ble_temp_callback,
+				.val_handle = &( ble_state.temp_handle ),
+				.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE |
+					BLE_GATT_CHR_F_NOTIFY,
+				/* Notify sends a 32 bit float with the current
+				 * temperature.
+				 *
+				 * Read sends buffered temperature data in this format
+				 *
+				 * 	struct __attribute__((packed)) {
+				 * 		uint16_t length;
+				 * 		uint16_t interval;
+				 * 		float data[
+				 * 			length > MAX_TEMPERATURE_TRANSFER_SIZE ?
+				 * 			MAX_TEMPERATURE_TRANSFER_SIZE : length];
+				 * 	};
+				 *
+				 * length will be the total number of data points
+				 * that are left to send, but reads can only send
+				 * so much data so you get max, 50 at a time.  Keep
+				 * reading until you get a full length's worth of
+				 * data.
+				 *
+				 * Writing should be a 16 bit integer with the
+				 * number of seconds to wait between temperature
+				 * readings.
+				 *
+				 * Updating the reading interval or disconnecting
+				 * from notifications empties the readings buffer,
+				 * so make sure you've read all that you want
+				 * before doing that. */
 			},
-			{ 0 },
+			{ 0, },
 		},
 	},
 	{ 0, }
@@ -140,6 +191,8 @@ static const struct ble_gatt_svc_def services[] = {
 
 int main( void ) {
 
+	(void) text8;
+
 	sysinit();
 
 	screen_init();
@@ -148,11 +201,21 @@ int main( void ) {
 
 	gpio_init();
 
-	t = 0;
-	static struct os_callout timer;
-	os_callout_init( &timer, os_eventq_dflt_get(),
-		&timeout, &timer );
-	os_callout_reset( &timer, OS_TICKS_PER_SEC );
+	uv.state.period = 0xFFFF;
+	uv.state.on_duration = 0;
+	uv.state.point_in_cycle = 0;
+	uv.state.intensity = 0;
+	uv.on = false;
+	os_callout_init( &( uv.callout ), os_eventq_dflt_get(),
+		&uv_timeout, NULL );
+
+	temperature.buffer_start = 0;
+	temperature.buffer_end = 0;
+	temperature.interval = 10;
+	os_callout_init( &( temperature.callout ), os_eventq_dflt_get(),
+		&temperature_timeout, NULL );
+	os_callout_reset( &( temperature.callout ),
+		OS_TICKS_PER_SEC * temperature.interval );
 
 	while ( 1 ) {
 
@@ -370,8 +433,7 @@ static int ble_gap_event( struct ble_gap_event* event, void* data ) {
 
 			} else {
 
-				ble_state.indicate_status = 0;
-				ble_state.notify_status = 0;
+				ble_state.temp_notify_status = 0;
 				ble_state.connection_handle = 0;
 				ble_advertise();
 
@@ -383,8 +445,7 @@ static int ble_gap_event( struct ble_gap_event* event, void* data ) {
 
 		case BLE_GAP_EVENT_DISCONNECT: {
 
-			ble_state.indicate_status = 0;
-			ble_state.notify_status = 0;
+			ble_state.temp_notify_status = 0;
 			ble_state.connection_handle = 0;
 			ble_advertise();
 
@@ -395,16 +456,15 @@ static int ble_gap_event( struct ble_gap_event* event, void* data ) {
 		case BLE_GAP_EVENT_SUBSCRIBE: {
 
 			if ( event->subscribe.attr_handle ==
-					ble_state.indicate_handle ) {
-
-				ble_state.indicate_status =
-					event->subscribe.cur_indicate;
+					ble_state.temp_handle ) {
 
-			} else if ( event->subscribe.attr_handle ==
-					ble_state.notify_handle ) {
-
-				ble_state.notify_status =
+				ble_state.temp_notify_status =
 					event->subscribe.cur_notify;
+				
+				if ( !event->subscribe.cur_notify ) {
+					temperature.buffer_start = 0;
+					temperature.buffer_end = 0;
+				}
 
 			}
 
@@ -473,102 +533,114 @@ static int ble_info_callback( uint16_t connection_handle,
 
 }
 
-static int ble_rw_callback( uint16_t connection_handle,
+static int ble_uv_callback( uint16_t connection_handle,
 		uint16_t attribute_handle, struct ble_gatt_access_ctxt* context,
 		void* data ) {
 
 	(void) data;
-	(void) attribute_handle;
 	(void) connection_handle;
 
-	if ( 0 == ble_uuid_cmp( context->chr->uuid,
-			BLE_UUID128_DECLARE( READ_UUID ) ) ) {
-
-		if ( BLE_GATT_ACCESS_OP_WRITE_CHR == context->op ) {
+	assert( ble_state.uv_handle == attribute_handle );
 
-			return BLE_ATT_ERR_WRITE_NOT_PERMITTED;
+	if ( BLE_GATT_ACCESS_OP_WRITE_CHR == context->op ) {
 
-		} else if ( BLE_GATT_ACCESS_OP_READ_CHR == context->op ) {
+		os_mbuf_copydata( context->om, 0,
+			sizeof uv.state, &( uv.state ) );
 
-			static const char str[] = "Reading! Congratulations!";
-			if ( 0 != os_mbuf_append(
-					context->om, str, strlen( str ) ) ) {
-				return BLE_ATT_ERR_INSUFFICIENT_RES;
-			}
+		if ( uv.state.point_in_cycle < uv.state.on_duration ) {
 
-			memset( frame + frame_size / 4, 0, frame_size / 8 );
-			text8( "Read read", frame, 2, 0 );
-			i2c_write( frame_buffer, sizeof frame_buffer );
+			os_callout_reset( &( uv.callout ), OS_TICKS_PER_SEC *
+				( uv.state.on_duration - uv.state.point_in_cycle ) );
 
 		} else {
 
-			assert( 0 );
+			os_callout_reset( &( uv.callout ), OS_TICKS_PER_SEC *
+				( uv.state.period - uv.state.point_in_cycle ) );
 
 		}
 
-	} else if ( 0 == ble_uuid_cmp( context->chr->uuid,
-			BLE_UUID128_DECLARE( WRITE_UUID ) ) ) {
+	} else if ( BLE_GATT_ACCESS_OP_READ_CHR == context->op ) {
 
-		if ( BLE_GATT_ACCESS_OP_WRITE_CHR == context->op ) {
+		uint16_t time = os_callout_remaining_ticks(
+			&( uv.callout ), os_time_get() ) / OS_TICKS_PER_SEC;
+		if ( uv.on ) {
+			uv.state.point_in_cycle = uv.state.on_duration - time - 1;
+		} else {
+			uv.state.point_in_cycle = uv.state.period - time - 1;
+		}
 
-			char str[256] = { 0, };
-			os_mbuf_copydata( context->om, 0, sizeof str - 1, str );
+		os_mbuf_append( context->om, &( uv.state ), sizeof uv.state );
 
-			memset( frame + frame_size / 4, 0, frame_size / 8 );
-			text8( str, frame, 2, 0 );
-			i2c_write( frame_buffer, sizeof frame_buffer );
+	} else {
 
-		} else if ( BLE_GATT_ACCESS_OP_READ_CHR == context->op ) {
+		assert( 0 );
 
-			return BLE_ATT_ERR_READ_NOT_PERMITTED;
+	}
 
-		} else {
+	return 0;
 
-			assert( 0 );
+}
 
-		}
+static int ble_temp_callback( uint16_t connection_handle,
+		uint16_t attribute_handle, struct ble_gatt_access_ctxt* context,
+		void* data ) {
+
+	(void) data;
+	(void) connection_handle;
 
-	} else if ( 0 == ble_uuid_cmp( context->chr->uuid,
-			BLE_UUID128_DECLARE( READ_WRITE_UUID ) ) ) {
+	assert( ble_state.temp_handle == attribute_handle );
 
-		if ( BLE_GATT_ACCESS_OP_WRITE_CHR == context->op ) {
+	if ( BLE_GATT_ACCESS_OP_WRITE_CHR == context->op ) {
 
-			char str[256] = { 0, };
-			os_mbuf_copydata( context->om, 0, sizeof str, str );
+		os_mbuf_copydata( context->om, 0,
+			sizeof temperature.interval, &( temperature.interval ) );
 
-			memset( frame + frame_size / 4, 0, frame_size / 8 );
-			text8( str, frame, 2, 0 );
-			i2c_write( frame_buffer, sizeof frame_buffer );
+		temperature.buffer_start = 0;
+		temperature.buffer_end = 0;
 
-		} else if ( BLE_GATT_ACCESS_OP_READ_CHR == context->op ) {
+		os_callout_reset( &( temperature.callout ),
+			OS_TICKS_PER_SEC * temperature.interval );
 
-			static const char str[] = "R/W Reading! Congratulations!";
-			if ( 0 != os_mbuf_append(
-					context->om, str, strlen( str ) ) ) {
-				return BLE_ATT_ERR_INSUFFICIENT_RES;
-			}
-			memset( frame + frame_size / 4, 0, frame_size / 8 );
-			text8( "Read/write read", frame, 2, 0 );
-			i2c_write( frame_buffer, sizeof frame_buffer );
+	} else if ( BLE_GATT_ACCESS_OP_READ_CHR == context->op ) {
 
+		struct {
+			uint32_t length_interval;
+			float data[MAX_TEMPERATURE_TRANSFER_SIZE];
+		} data;
+
+		uint32_t length;
+		if ( temperature.buffer_end >= temperature.buffer_start ) {
+			length = temperature.buffer_end - temperature.buffer_start;
 		} else {
+			length = temperature.buffer_end + TEMPERATURE_BUFFER_LEN -
+				temperature.buffer_start;
+		}
 
-			assert( 0 );
+		data.length_interval = length << 16 | temperature.interval;
 
+		if ( length > MAX_TEMPERATURE_TRANSFER_SIZE ) {
+			length = MAX_TEMPERATURE_TRANSFER_SIZE;
 		}
 
-	}
+		for ( int i = 0; i < length; i++ ) {
+			size_t pos = ( temperature.buffer_start + i ) %
+				TEMPERATURE_BUFFER_LEN;
+			data.data[i] = temperature.buffer[pos];
+		}
 
-	return 0;
+		os_mbuf_append( context->om, &data,
+			sizeof (uint32_t) + length * sizeof (float) );
 
-}
+		temperature.buffer_start += length;
+		temperature.buffer_start %= TEMPERATURE_BUFFER_LEN;
 
-static int ble_in_callback( uint16_t connection_handle,
-		uint16_t attribute_handle, struct ble_gatt_access_ctxt* context,
-		void* data ) {
+	} else {
+
+		assert( 0 );
 
-	/* Notify / Indicate only */
-	return BLE_ATT_ERR_READ_NOT_PERMITTED;
+	}
+
+	return 0;
 
 }
 
@@ -598,68 +670,75 @@ static void gpio_init( void ) {
 
 static void button_handler( void* data ) {
 
-	int button = *( (int*) data );
+	(void) data;
+}
 
-	if ( 1 == button ) {
+static void uv_timeout( struct os_event* event ) {
 
-		ble_state.count--;
+	if ( uv.on ) {
 
-		if ( ble_state.indicate_status ) {
+		hal_gpio_write( LED_RED, 1 );
+		hal_gpio_write( LED_GREEN, 1 );
+		hal_gpio_write( LED_BLUE, 1 );
 
-			struct os_mbuf* om =
-				ble_hs_mbuf_from_flat( &( ble_state.count ), 1 );
-			assert( 0 == ble_gatts_indicate_custom(
-				ble_state.connection_handle,
-				ble_state.indicate_handle, om ) );
+		os_callout_reset( &( uv.callout ),
+			OS_TICKS_PER_SEC *
+				( uv.state.period - uv.state.on_duration ) );
 
-		}
+		uv.on = false;
 
 	} else {
 
-		ble_state.count++;
-
-		if ( ble_state.indicate_status ) {
+		if ( 0 == uv.state.on_duration ) {
 
-			struct os_mbuf* om =
-				ble_hs_mbuf_from_flat( &( ble_state.count ), 1 );
-			assert( 0 == ble_gatts_indicate_custom(
-				ble_state.connection_handle,
-				ble_state.indicate_handle, om ) );
+			return;
 
 		}
 
-	}
+		hal_gpio_write( LED_RED, !( uv.state.intensity < 0x8000 ) );
+		hal_gpio_write( LED_GREEN, !( uv.state.intensity >= 0x8000 &&
+			uv.state.intensity < 0xC000 ) );
+		hal_gpio_write( LED_BLUE, !( uv.state.intensity >= 0xC000 ) );
 
-	char str[128];
-	snprintf( str, sizeof str, "Count: %d", ble_state.count );
-	memset( frame + frame_size / 8 * 3, 0, frame_size / 8 );
-	text8( str, frame, 3, 0 );
-	i2c_write( frame_buffer, sizeof frame_buffer );
+		os_callout_reset( &( uv.callout ),
+			OS_TICKS_PER_SEC * uv.state.on_duration );
 
-}
+		uv.on = true;
 
-static void timeout( struct os_event* event ) {
+	}
 
-	t++;
+}
 
-	char str[32];
-	snprintf( str, sizeof str,
-		"Time: %lu:%02lu", t / 60, t % 60 );
-	memset( frame + frame_size / 2, 0, frame_size / 8 );
-	text8( str, frame, 4, 0 );
-	i2c_write( frame_buffer, sizeof frame_buffer );
+static void temperature_timeout( struct os_event* event ) {
 
-	if ( ble_state.notify_status ) {
+	static float current_temperature = 37.0;
 
-		struct os_mbuf* om =
-			ble_hs_mbuf_from_flat( str, strlen( str ) );
+	if ( ble_state.temp_notify_status ) {
+
+		struct os_mbuf* om = ble_hs_mbuf_from_flat(
+			&current_temperature, sizeof current_temperature );
 		assert( 0 == ble_gatts_notify_custom(
 			ble_state.connection_handle,
-			ble_state.notify_handle, om ) );
+			ble_state.temp_handle, om ) );
+
+	} else {
+
+		temperature.buffer[temperature.buffer_end] =
+			current_temperature;
+		temperature.buffer_end++;
+		temperature.buffer_end %= TEMPERATURE_BUFFER_LEN;
+		if ( temperature.buffer_end == temperature.buffer_start ) {
+			temperature.buffer_start++;
+			temperature.buffer_start %= TEMPERATURE_BUFFER_LEN;
+		}
 
 	}
 
-	struct os_callout* timer = (struct os_callout*) event->ev_arg;
-	os_callout_reset( timer, OS_TICKS_PER_SEC );
+	current_temperature += 0.1;
+	if ( current_temperature >= 38 ) {
+		current_temperature = 36.1;
+	}
 
+	os_callout_reset( &( temperature.callout ),
+		OS_TICKS_PER_SEC * temperature.interval );
 }
diff --git a/apps/ble-demo/syscfg.yml b/apps/ble-demo/syscfg.yml
index cfbd94fb7198afd17bc404cf946d4ae4c23d0db4..6e9b210d7efb5f8f263ae3a26bc443981d402cfb 100644
--- a/apps/ble-demo/syscfg.yml
+++ b/apps/ble-demo/syscfg.yml
@@ -7,7 +7,7 @@ syscfg.defs:
         value: '"0000"'
     DEVICE_MANUFACTURER:
         description: "Device manufacturer string"
-        value: '"Tom :)"'
+        value: '"C-FLEET :)"'
     CPUTIME_FREQ:
         description: "Frequency for the cputime counter"
         value: '50000'