// Bluescan // by Francis Li // http://www.francisli.com/ // // inspired by // // Jabberwocky // by Intel Research Berkeley // http://www.urban-atmospheres.net/Jabberwocky/index.htm // // Posted January 2, 2006 // // Bluescan discovers nearby bluetooth devices and represents them on the screen // as a cellphone icon (even though they may be PCs or other devices). As they are discovered, // they appear at the top of the screen, then slowly drift down until they disappear at the bottom. // Newly discovered devices are red, devices you've encountered before are green. The more times // you encounter a device, the bigger the icon will be. // import processing.bluetooth.*; final String SOFTKEY_DISCOVER = "Discover"; final String SOFTKEY_CANCEL = "Cancel"; PFont font; Bluetooth bt; boolean discovering; CellphoneIcon[] icons; int numIcons; int selected; Record[] records; int numRecords; int prev; void setup() { //// get and set the default system font font = loadFont(); textFont(font); //// instantiate a new bluetooth object bt = new Bluetooth(this); //// initialize the records array to a default size records = new Record[8]; numRecords = 0; //// initialize the icons array to a default size icons = new CellphoneIcon[8]; numIcons = 0; //// set up a command softkey to start discovery process softkey(SOFTKEY_DISCOVER); //// cap framerate framerate(20); //// align everything center textAlign(CENTER); //// load previously saved records String[] savedRecords = loadStrings("records.txt"); int numSaved = savedRecords.length; Record r; for (int i = 0; i < numSaved; i++) { r = new Record(savedRecords[i]); addRecord(r); } } void destroy() { //// before the midlet is destroyed, save records String[] savedRecords = new String[numRecords]; for (int i = 0; i < numRecords; i++) { savedRecords[i] = records[i].toString(); } saveStrings("records.txt", savedRecords); } void draw() { //// set a nice blue sea background background(0xff0099ff); //// update frame timing int current = millis(); int elapsed = current - prev; prev = current; //// move icons based on elapsed time for (int i = 0; i < numIcons; i++) { icons[i].move(elapsed); if (icons[i].ty >= (height - icons[i].height() / 2)) { icons[i].tscale_fp = 0; } } //// if icons have reached the bottom, remove them from the array for (int i = numIcons - 1; i >= 0; i--) { if (icons[i].scale_fp == 0) { icons[i] = null; arraycopy(icons, i + 1, icons, i, numIcons - i - 1); numIcons--; selected--; } } if (selected < 0) { selected = 0; } //// draw the icons for (int i = 0; i < numIcons; i++) { icons[i].draw(); } //// draw name of current selection if (selected < numIcons) { Record r = icons[selected].r; //// draw the name under the icon if (r.name == Device.UNKNOWN) { //// if the record name is unknown, check the device to see if it has //// been updated in the background discovery process r.name = r.device.name; } int width = textWidth(r.name); stroke(0); fill(255); rect(icons[selected].x - width / 2 - 2, icons[selected].y + icons[selected].height() / 2, width + 4, font.height + 4); fill(0); text(r.name, icons[selected].x, icons[selected].y + icons[selected].height() / 2 + font.baseline + 2); } //// draw the current number of visible devices text(str(numIcons), width / 2, height); } void keyPressed() { if (key == '5') { //// testing animation on the emulator, just puts a dummy device/icon on the screen Record r = new Record(); r.name = "Test"; r.count = random(5); addIcon(r); } else if (keyCode == LEFT) { //// update selection if (numIcons > 0) { selected = (selected - 1 + numIcons) % numIcons; } } else if (keyCode == RIGHT) { //// update selection if (numIcons > 0) { selected = (selected + 1) % numIcons; } } } void softkeyPressed(String label) { if (label.equals(SOFTKEY_DISCOVER)) { //// start discovery process discovering = true; bt.discover(); softkey(SOFTKEY_CANCEL); } else if (label.equals(SOFTKEY_CANCEL)) { //// cancel discovery process discovering = false; bt.cancel(); softkey(SOFTKEY_DISCOVER); } } void libraryEvent(Object library, int event, Object data) { if (library == bt) { switch (event) { case Bluetooth.EVENT_DISCOVER_DEVICE: //// new device discovered! Device d = (Device) data; Record r = null; String address = d.address; boolean found = false; //// first, look for it in existing records list for (int i = 0; i < numRecords; i++) { if (records[i].address.equals(address)) { /// found! update count and last encounter time r = records[i]; r.count++; //// a bit of a hack to get an absolute system time (unlike millis() which is relative to app start) r.last = (int) (System.currentTimeMillis() / 1000); r.device = d; found = true; break; } } if (found) { found = false; //// see if it is currently on screen for (int i = 0; i < numIcons; i++) { if (icons[i].r == r) { icons[i].tx = icons[i].height() / 2; found = true; break; } } if (!found) { //// put it on screen! addIcon(r); } } else { //// not found, so create a new record r = new Record(); r.name = d.name; r.address = d.address; r.count = 1; r.last = (int) (System.currentTimeMillis() / 1000); r.device = d; addRecord(r); addIcon(r); } break; case Bluetooth.EVENT_DISCOVER_DEVICE_COMPLETED: //// done, reset command key and state discovering = false; softkey(SOFTKEY_DISCOVER); break; } } } //// adds a record to the array, expanding it if necessary void addRecord(Record r) { records[numRecords] = r; numRecords++; if (numRecords == records.length) { Record[] oldRecords = records; records = new Record[2 * numRecords]; arraycopy(oldRecords, 0, records, 0, numRecords); } } //// puts an icon on the screen void addIcon(Record r) { CellphoneIcon i = new CellphoneIcon(); i.r = r; i.tscale_fp = (3 + r.count) * ONE / 4; i.x = i.tx = 10 + random(width - 20); i.y = i.ty = i.height() / 2; icons[numIcons] = i; numIcons++; if (numIcons == icons.length) { CellphoneIcon[] oldIcons = icons; icons = new CellphoneIcon[2 * numIcons]; arraycopy(oldIcons, 0, icons, 0, numIcons); } } //// a data structure to store device info, and to read/write it from strings class Record { String name; String address; int count; int last; Device device; public Record() { } public Record(String savedRecord) { String[] parts = split(savedRecord, ","); name = parts[0]; address = parts[1]; count = int(parts[2]); last = int(parts[3]); } String toString() { return name + "," + address + "," + count + "," + last; } } //// a data structure to store icon info, as well as render it to screen class CellphoneIcon { /** Reference to device history record object */ Record r; /** the actual x, y location on the screen */ int x, y; /** the actual scale of the icon on the screen */ int scale_fp; /** accumulated distance */ int dx_fp, dy_fp; /** the target x, y, and scale */ int tx, ty, tscale_fp; /** accumulated distance of target motion */ int dty_fp; /** x, y velocities */ int vx_fp, vy_fp; int width() { return fptoi(tscale_fp * 12); } int height() { return fptoi(tscale_fp * 30); } void move(int elapsed) { //// quickly reach target scale int dscale_fp = (tscale_fp - scale_fp) / 2; if (dscale_fp == 0) { scale_fp = tscale_fp; } else { scale_fp += dscale_fp; } //// target y moves downward at constant velocity dty_fp += elapsed * itofp(8) / 1000; if (dty_fp > ONE) { ty += fptoi(floor(dty_fp)); dty_fp = dty_fp - floor(dty_fp); } //// target x randomly shifts tx += random(-2, 2); if (tx < 10) { tx = 10; } else if (tx > (width - 10)) { tx = width - 10; } //// icon accelerates towards target vy_fp += (ty - y) * itofp(2) * elapsed / 1000; if (vy_fp > itofp(16)) { vy_fp = itofp(16); } else if (vy_fp < -itofp(16)) { vy_fp = -itofp(16); } dy_fp += elapsed * vy_fp / 1000; if ((dy_fp > ONE) || (dy_fp < -ONE)) { y += fptoi(floor(dy_fp)); dy_fp = dy_fp - floor(dy_fp); } vx_fp += (tx - x) * itofp(2) * elapsed / 1000; if (vx_fp > itofp(16)) { vx_fp = itofp(16); } else if (vx_fp < -itofp(16)) { vx_fp = -itofp(16); } dx_fp += elapsed * vx_fp / 1000; if ((dx_fp > ONE) || (dx_fp < -ONE)) { x += fptoi(floor(dx_fp)); dx_fp = dx_fp - floor(dx_fp); } } void draw() { noStroke(); if (r.count > 1) { fill(0xff00ff00); } else { fill(0xffff0000); } //// antenna rect(x - fptoi(scale_fp * 12) / 2 + fptoi(scale_fp * 12) - fptoi(scale_fp * 3), y - fptoi(scale_fp * 25) / 2 - fptoi(scale_fp * 5), fptoi(scale_fp * 3), fptoi(scale_fp * 5)); //// body rect(x - fptoi(scale_fp * 12) / 2, y - fptoi(scale_fp * 25) / 2, fptoi(scale_fp * 12), fptoi(scale_fp * 25)); //// screen fill(230); rect(x - fptoi(scale_fp * 12) / 2 + fptoi(scale_fp * 2), y - fptoi(scale_fp * 25) / 2 + fptoi(scale_fp * 2), fptoi(scale_fp * 12) - 2 * fptoi(scale_fp * 2), fptoi(scale_fp * 8)); } }