Joined: Mon Feb 21, 2011 10:03 am Posts: 26 Location: San Diego, CA
|
Here's the final version of the Chumby->inPulse hack, as discussed in this threadinPulse code: /** * * Image Viewer * * This app accepts simple bitmap drawing commands from another device * and renders them on the display. It supports blanking the display, * filling a rectangle with a single color, and two commands for filling * a rectangle with multiple colors - one for raw pixels, and one for * for run-length encoded pixels. * * Copyright (C) 2011, Duane Maxwell [email protected] * * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. **/
#include <pulse_os.h> #include <pulse_types.h> #include <stdint.h>
#define SLEEP_DELAY 30000 #define SNIFF_LEVEL 400
const color24_t const logoColors[16] = { {0x00,0x00,0x00,0x00}, {0x01,0x01,0x20,0x00}, {0x01,0x03,0x04,0x00}, {0x03,0x06,0x0A,0x00}, {0x06,0x0A,0x12,0x00}, {0x0A,0x12,0x20,0x00}, {0x0F,0x1B,0x30,0x00}, {0x14,0x25,0x41,0x00}, {0x1F,0x36,0x5F,0x00}, {0x26,0x48,0x84,0x00}, {0x2B,0x51,0x93,0x00}, {0x30,0x5B,0xA7,0x00}, {0x39,0x67,0xB8,0x00}, {0x4D,0x78,0xC2,0x00}, {0x90,0xA8,0xD4,0x00}, {0xC2,0xD0,0xE8,0x00} };
const char *logoMap = "AAAAAAAAAAAAEFHIIJJKKJICAAAAA" "AAAAAAAAAAFILMNNNNNNNNNIAAAAA" "AAAAAAAAAINNNNMMMMMMMMMMFAAAA" "AAAAAAAAHNNMMMMLLLLLMMMMIAAAA" "AAAAAAAAKNMLNOMLLLMOMLMMIAAAA" "AAAAAAADLMMNPOPLLLPOPLLMIAAAA" "AAAAAAACKMLOOLPNKLPOPLLMIAAAA" "AAAAAAAAJMLNPPOLKKMONLLLHAAAA" "AAAAAAAAILLLMNLKKKKKKLLLGAAAA" "AAAAAAAAHLLKKKKKKKKKKKLKFAAAA" "AAAAAAEEILKKKJJJJJJKKKKKFAAAA" "AACGIJKKLLKKJJJJJJJJKKKLJFAAA" "AGKMMLLLLKKJJJJJJJJJJKKLLKGAA" "ILLKKJJKKKJJJJJJJJJJJJIKLLKGA" "HHGFEDGKKJJJJJJJJJJJJJEGJLLJD" "AAAAAEKKKIHJJJIJJJIJKKGAEIKLH" "AAAAAILKIEHJJIGJJIDIKKIAAAGII" "AAAAEJKJDEKJJFHKJIAHKKJCAAACD" "AAAAFKKFAIKKIAIKKHADJKJCAAAAA" "AAAAFJGAAJKJEAJKKGAAGKJAAAAAA" "AAAADFAAAJJFAAIKJEAAAHIAAAAAA" "AAAAAAAAAIFAAAHKHAAAAACAAAAAA" "AAAAAAAAAAAAAAEIDAAAAAAAAAAAA";
void drawLogo() { int x = 33; int y = 52; pulse_set_draw_window(x,y,x+28,y+22); const char *map = logoMap; const color24_t *colors = logoColors; for (int i=0;i<29*23; i++) { pulse_draw_point24(colors[*map++-'A']); } }
// // this is the data structure corresponding to the Bluetooth packet // sent by the external device. // #define MAGIC 'C'
enum { CLEAR = 0, FILL_RECT = 1, FILL_PIXELS = 2, FILL_PIXELS_RLE = 3 };
typedef struct { uint8_t magic; // should be MAGIC uint8_t command; // from the above enum union _u { struct { // data for FILL_RECT uint8_t left; uint8_t top; uint8_t right; uint8_t bottom; color24_t color; } fill; struct { // data for FILL_PIXELS uint8_t left; uint8_t top; uint8_t right; uint8_t bottom; color24_t colors[]; } pixels; struct { // data for FILL_PIXELS_RLE uint8_t left; uint8_t top; uint8_t right; uint8_t bottom; uint8_t data[]; } rle; } u; } __attribute__((packed)) command;
// // RLE pixels are stored as a sequence of runs // each run starts with a count byte - if the high bit of the count is set, // then it should emit a run of the rest of the byte with just the next color // if the high bit is clear, then it's a run of different colors // void handle_received_bluetooth_data(const uint8_t *buffer) { pulse_oled_set_brightness(100); pulse_update_power_down_timer(SLEEP_DELAY); command *c = (command *)buffer; if (c->magic==MAGIC) { switch (c->command) { case CLEAR: { pulse_blank_canvas(); } break; case FILL_RECT: { pulse_set_draw_window(c->u.fill.left, c->u.fill.top, c->u.fill.right, c->u.fill.bottom); int count = (c->u.fill.right-c->u.fill.left+1)*(c->u.fill.bottom-c->u.fill.top+1); color24_t color = c->u.fill.color; while (count--) pulse_draw_point24(color); } break; case FILL_PIXELS: { pulse_set_draw_window(c->u.pixels.left, c->u.pixels.top, c->u.pixels.right, c->u.pixels.bottom); int count = (c->u.pixels.right-c->u.pixels.left+1)*(c->u.pixels.bottom-c->u.pixels.top+1); color24_t *color = c->u.pixels.colors; while (count--) pulse_draw_point24(*color++); } break; case FILL_PIXELS_RLE: { pulse_set_draw_window(c->u.rle.left, c->u.rle.top, c->u.rle.right, c->u.rle.bottom); int count = (c->u.pixels.right-c->u.pixels.left+1)*(c->u.pixels.bottom-c->u.pixels.top+1); uint8_t *data = c->u.rle.data; while (count) { uint8_t pixelCount = *data++; if (pixelCount & 0x80) { // a run of pixelCount&0x7f pixels of the same color pixelCount &= 0x7f; count -= pixelCount; color24_t *color = (color24_t *)data; data += sizeof(color24_t); while (pixelCount--) { pulse_draw_point24(*color); } } else { // run of pixelCount different colors count -= pixelCount; color24_t *color = (color24_t *) data; while (pixelCount--) { pulse_draw_point24(*color++); } data = (uint8_t *)color; } } } break; default: printf("BAD CMD %d\n",c->command); break; } } else { printf("BAD MAG %d\n",c->magic); } }
void handle_button_causing_wakeup();
void main_app_init() { pulse_blank_canvas(); drawLogo(); pulse_oled_set_brightness(100); pulse_update_power_down_timer(SLEEP_DELAY); pulse_register_callback(ACTION_WOKE_FROM_BUTTON, &handle_button_causing_wakeup); pulse_register_callback(ACTION_HANDLE_NON_PULSE_PROTOCOL_BLUETOOTH_DATA, (PulseCallback)&handle_received_bluetooth_data); pulse_set_bluetooth_sniff(true, SNIFF_LEVEL); }
void handle_button_causing_wakeup() { pulse_blank_canvas(); drawLogo(); pulse_oled_set_brightness(100); pulse_update_power_down_timer(SLEEP_DELAY); }
void main_app_handle_button_down() { pulse_update_power_down_timer(SLEEP_DELAY); }
void main_app_handle_button_up() { }
void main_app_loop() { }
void main_app_handle_doz() { for (int i = 100; i >= 0; i-=6) { pulse_oled_set_brightness(i); pulse_mdelay(60); } }
void main_app_handle_hardware_update(enum PulseHardwareEvent event) { }
This code is actually very generic and can be used to send pretty much any bitmap to the device. Chumby code (requires BlueZ 2.x installed): /** * * Name: ctoi * * Description: * * This app transfers a screen shot from a Falconwing (aka Chumby One) * device to an Allerta inPulse Bluetooth watch. * * This app requires the device to have a USB Bluetooth dongle, the * appropriate drivers (very likely already in the firmware), and a * the BlueZ library - probably an old one (2.x) since the newer * ones require D-Bus, which is not supported on the chumby * * Copyright (C) 2011, Duane Maxwell [email protected] * * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. **/
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/socket.h> #include <bluetooth/bluetooth.h> #include <bluetooth/l2cap.h>
// // the chumby screen is 320x240 of RGB565LE pixels // #define LARGE_WIDTH 320 #define LARGE_HEIGHT 240 #define LARGE_PIXELS (LARGE_WIDTH*LARGE_HEIGHT) #define LARGE_PIXEL_SIZE sizeof(uint16_t) #define LARGE_SIZE (LARGE_PIXELS*LARGE_PIXEL_SIZE)
// // this is the datatype for an inPulse pixel // typedef struct { uint8_t red; uint8_t green; uint8_t blue; uint8_t alpha; } __attribute__((packed)) color24_t;
#define SMALL_WIDTH 80 #define SMALL_HEIGHT 60 #define SMALL_PIXELS (SMALL_WIDTH*SMALL_HEIGHT) #define SMALL_PIXEL_SIZE sizeof(color24_t) #define SMALL_SIZE (SMALL_PIXELS*SMALL_PIXEL_SIZE)
#define H_OFFSET 8 #define V_OFFSET 34
uint16_t *largePixels; // the chumby screen uint16_t **large; // line starts color24_t *smallPixels; // the thumbnail color24_t **small; // line starts
// // allocate buffers, set up line starts arrays // void init() { int i; largePixels = (uint16_t *)malloc(LARGE_SIZE); large = (uint16_t **)malloc(LARGE_HEIGHT*sizeof(uint16_t *)); for (i=0;i<LARGE_HEIGHT;i++) { large[i] = &largePixels[i*LARGE_WIDTH]; } smallPixels = (color24_t *)malloc(SMALL_SIZE); small = (color24_t **)malloc(SMALL_HEIGHT*sizeof(color24_t *)); for (i=0;i<SMALL_HEIGHT;i++) { small[i] = &smallPixels[i*SMALL_WIDTH]; } }
// // all we do here is open the screen as a raw file and copy the entire thing into // and array of RGB565 pixels // void copyScreen() { FILE *f = fopen("/dev/fb0","r"); if (f==NULL) { fprintf(stderr,"Cannot open frame buffer\n"); exit(1); } size_t count = fread(largePixels,1,LARGE_SIZE,f); fclose(f); }
// // This code is somewhat optimized based on the fact that the reduced image is exactly // one quarter the screen size in each dimension, or 1/16th the area. What it does // is take each component of the source RGB565 pixel and add only the upper 4 bits into // the final color - that way the 16 colors accumulate and the resulting color is already // in the correct range. // void reduceScreen() { int x,y,lx,ly; for (y=0; y<SMALL_HEIGHT; y++) { color24_t *s = small[y]; for (x=0; x<SMALL_WIDTH; x++) { s->red = s->green = s->blue = s->alpha = 0; for (ly=y*4;ly<(y+1)*4;ly++) { for (lx=x*4;lx<(x+1)*4;lx++) { uint16_t lpixel = large[ly][lx]; int r = (lpixel >> 12) & 0xf; int g = (lpixel >> 7) & 0xf; int b = (lpixel >> 1) & 0xf; s->red += r; s->green += g; s->blue += b; } } s++; } } }
// // this is the data structure for the Bluetooth packet we send to the watch // we make sure it does not look like an inPulse notification // #define MAGIC 'C' enum { CLEAR =0, FILL_RECT = 1, FILL_PIXELS = 2, FILL_PIXELS_RLE = 3 };
typedef struct { uint8_t magic; // should be MAGIC uint8_t command; union _u { struct { uint8_t left; uint8_t top; uint8_t right; uint8_t bottom; color24_t color; } fill; struct { uint8_t left; uint8_t top; uint8_t right; uint8_t bottom; color24_t colors[]; } pixels; struct { uint8_t left; uint8_t top; uint8_t right; uint8_t bottom; uint8_t data[]; } rle; } u; } __attribute__((packed)) command;
// // here we emit the thumbnail image as a set of 40x1 rectangles of raw pixels // this is because the packet size is limited to 220 or so bytes // void emitScreen(char *bdaddr) { struct sockaddr_l2 addr = { 0 }; int s, status;
printf("Creating socket..."); s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
addr.l2_family = AF_BLUETOOTH; addr.l2_psm = htobs(0x1001); str2ba( bdaddr, &addr.l2_bdaddr); printf("Connecting..."); status = connect(s, (struct sockaddr *)&addr, sizeof(addr));
int size = 40*sizeof(color24_t)+6*sizeof(uint8_t); // 40 pixels worth of data command *c = (command *)malloc(size); c->magic = MAGIC; c->command = FILL_PIXELS;
if (status==0) { int y; printf("Writing data..."); for (y=0;y<SMALL_HEIGHT;y++) { c->u.pixels.left = H_OFFSET; c->u.pixels.right = H_OFFSET+39; c->u.pixels.top = c->u.pixels.bottom = y+V_OFFSET; memcpy(c->u.pixels.colors,small[y],40*sizeof(color24_t)); status = write(s,c,size); c->u.pixels.left = H_OFFSET+40; c->u.pixels.right = H_OFFSET+79; memcpy(c->u.pixels.colors,&small[y][40],40*sizeof(color24_t)); status = write(s,c,size); } }
if (status < 0) perror("uh oh!");
printf("Closing socket\n"); close(s); }
int main(int argc,char **argv) { if (argc<2) { printf("usage: ctoi <bt-addr>\n"); exit(0); } init(); for (;;) { copyScreen(); reduceScreen(); emitScreen(argv[1]); sleep(5); } }
|
|