|
| 1 | +""" |
| 2 | +Circuit Python BLE Color Patchwork |
| 3 | +This demo uses advertising to broadcast a color of the users choice. |
| 4 | +We will show a color "patch" on the screen for every unique device |
| 5 | +advertisement that we find. |
| 6 | +""" |
| 7 | + |
| 8 | +import time |
| 9 | +import random |
| 10 | +import board |
| 11 | +from adafruit_clue import clue |
| 12 | +import displayio |
| 13 | +from adafruit_ble import BLERadio |
| 14 | +from adafruit_ble.advertising.adafruit import AdafruitColor |
| 15 | +from adafruit_display_shapes.rect import Rect |
| 16 | +from adafruit_display_text import label |
| 17 | +import terminalio |
| 18 | + |
| 19 | +MODE_COLOR_SELECT = 0 |
| 20 | +MODE_SHOW_PATCHWORK = 1 |
| 21 | + |
| 22 | +COLOR_TRANSPARENT_INDEX = 0 |
| 23 | +COLOR_OFFWHITE_INDEX = 1 |
| 24 | + |
| 25 | +PROXIMITY_LIMIT = 100 |
| 26 | + |
| 27 | +current_mode = MODE_SHOW_PATCHWORK |
| 28 | + |
| 29 | +# The color pickers will cycle through this list with buttons A and B. |
| 30 | +color_options = [ |
| 31 | + 0xEE0000, |
| 32 | + 0xEEEE00, |
| 33 | + 0x00EE00, |
| 34 | + 0x00EEEE, |
| 35 | + 0x0000EE, |
| 36 | + 0xEE00EE, |
| 37 | + 0xCCCCCC, |
| 38 | + 0xFF9999, |
| 39 | + 0x99FF99, |
| 40 | + 0x9999FF, |
| 41 | +] |
| 42 | + |
| 43 | + |
| 44 | +# make all pixels in the patchwork bitmap transparent |
| 45 | +def make_transparent(): |
| 46 | + palette_mapping.make_transparent(0) |
| 47 | + for i in range(0, 8): |
| 48 | + for j in range(0, 8): |
| 49 | + bitmap[i, j] = 0 |
| 50 | + |
| 51 | + |
| 52 | +# make the pixels in the patchwork white by setting them in the palette |
| 53 | +def make_white(): |
| 54 | + for i in range(2, 66): |
| 55 | + palette_mapping[i] = 0xFFFFFF |
| 56 | + |
| 57 | + |
| 58 | +# draw the patchwork grid based on nearby_colors |
| 59 | +def draw_grid(): |
| 60 | + for i, color in enumerate(nearby_colors): |
| 61 | + if i < 64: |
| 62 | + palette_mapping[i + 2] = ( |
| 63 | + color & 0xFFFFFF |
| 64 | + ) # Mask 0xFFFFFF to avoid invalid color. |
| 65 | + print(i) |
| 66 | + print(color) |
| 67 | + |
| 68 | + |
| 69 | +# create a fake mac address and color for testing |
| 70 | +def add_fake(): |
| 71 | + fake_mac = "".join([random.choice("0123456789abcdef") for _ in range(10)]) |
| 72 | + fake_color = random.choice(color_options) |
| 73 | + nearby_addresses.append(fake_mac) |
| 74 | + nearby_colors.append(fake_color) |
| 75 | + |
| 76 | + |
| 77 | +# find nearby devices advertising colors |
| 78 | +def ble_scan(): |
| 79 | + print("scanning") |
| 80 | + # loop over all found devices |
| 81 | + for entry in ble.start_scan(AdafruitColor, minimum_rssi=-100, timeout=1): |
| 82 | + # if this device is not in the list already |
| 83 | + if entry.color in color_options: |
| 84 | + print("new color") |
| 85 | + if entry.address.address_bytes not in nearby_addresses: |
| 86 | + # print(entry.color) |
| 87 | + # add the address and color to respective lists |
| 88 | + nearby_addresses.append(entry.address.address_bytes) |
| 89 | + nearby_colors.append(entry.color) |
| 90 | + else: # address was already in the list |
| 91 | + # update the color to currently advertised value |
| 92 | + _index = nearby_addresses.index(entry.address.address_bytes) |
| 93 | + nearby_colors[_index] = entry.color |
| 94 | + |
| 95 | + |
| 96 | +# set a new color to be advertised |
| 97 | +def change_advertisement(color): |
| 98 | + ble.stop_advertising() |
| 99 | + advertisement.color = color |
| 100 | + ble.start_advertising(advertisement) |
| 101 | + # set NeoPixel to selected advertised |
| 102 | + clue.pixel.fill(color) |
| 103 | + # update top left self patch |
| 104 | + nearby_colors[0] = color |
| 105 | + |
| 106 | + |
| 107 | +# BLE Setup |
| 108 | +ble = BLERadio() |
| 109 | +advertisement = AdafruitColor() |
| 110 | +advertisement.color = color_options[0] |
| 111 | + |
| 112 | +# init neopixel |
| 113 | +clue.pixel.fill(color_options[0]) |
| 114 | +clue.pixel.brightness = 0.05 |
| 115 | + |
| 116 | +display = board.DISPLAY |
| 117 | + |
| 118 | +# Create a bitmap with two colors + 64 colors for the map |
| 119 | +bitmap = displayio.Bitmap(8, 8, 64 + 2) |
| 120 | + |
| 121 | +# Create a 8*8 bitmap pre-filled with 64 colors (color 0 and 1 are reserved) |
| 122 | +for _i in range(0, 8): |
| 123 | + for _j in range(0, 8): |
| 124 | + bitmap[_i, _j] = 2 + _i + _j * 8 |
| 125 | + |
| 126 | +# Create an empty palette that will be used in one to one mapping |
| 127 | +palette_mapping = displayio.Palette(64 + 2) |
| 128 | + |
| 129 | +palette_mapping[0] = 0x000000 |
| 130 | +palette_mapping[1] = 0xFFFFFF |
| 131 | + |
| 132 | +color_select_palette = displayio.Palette(len(color_options)) |
| 133 | +for _i, _color in enumerate(color_options): |
| 134 | + color_select_palette[_i] = _color |
| 135 | + |
| 136 | +# Color Select Layout |
| 137 | +color_select_group = displayio.Group(max_size=10) |
| 138 | +color_select_text_group = displayio.Group(max_size=4, scale=3) |
| 139 | + |
| 140 | +# white backrground |
| 141 | +background = Rect(0, 0, 240, 240, fill=0xFFFFFF) |
| 142 | +center_line = Rect(119, 0, 2, 180, fill=0x000000) |
| 143 | + |
| 144 | +# box around the color preview |
| 145 | +bottom_box = Rect(79, 174, 80, 80, fill=0xFFFFFF, outline=0x000000, stroke=2) |
| 146 | + |
| 147 | +left_btn_lbl = label.Label(terminalio.FONT, text="A", color=0x000000) |
| 148 | +right_btn_lbl = label.Label(terminalio.FONT, text="B", color=0x000000) |
| 149 | + |
| 150 | +left_btn_action = label.Label( |
| 151 | + terminalio.FONT, text="Next\nColor", color=0x000000, line_spacing=1 |
| 152 | +) |
| 153 | +right_btn_action = label.Label(terminalio.FONT, text="Save", color=0x000000) |
| 154 | + |
| 155 | +color_select_text_group.append(left_btn_lbl) |
| 156 | +color_select_text_group.append(right_btn_lbl) |
| 157 | + |
| 158 | +color_select_text_group.append(left_btn_action) |
| 159 | +color_select_text_group.append(right_btn_action) |
| 160 | + |
| 161 | +# x position centered on 25% of screen |
| 162 | +left_btn_lbl.anchor_point = (0.5, 0) |
| 163 | +left_btn_lbl.anchored_position = ((240 / 4) // 3, 21 // 3) |
| 164 | + |
| 165 | +# x position centered on 75% of screen |
| 166 | +right_btn_lbl.anchor_point = (0.5, 0) |
| 167 | +right_btn_lbl.anchored_position = ((240 / 4), 21 // 3) |
| 168 | + |
| 169 | +# x position centered on 25% of screen |
| 170 | +left_btn_action.anchor_point = (0.5, 0) |
| 171 | +left_btn_action.anchored_position = ((240 / 4) // 3, 96 // 3) |
| 172 | + |
| 173 | +# x position centered on 75% of screen |
| 174 | +right_btn_action.anchor_point = (0.5, 0) |
| 175 | +right_btn_action.anchored_position = ((240 / 4), 96 // 3) |
| 176 | + |
| 177 | +color_select_group.append(background) |
| 178 | +color_select_group.append(center_line) |
| 179 | +color_select_group.append(bottom_box) |
| 180 | +color_select_group.append(color_select_text_group) |
| 181 | + |
| 182 | +# color preview bmp |
| 183 | +color_select_preview_bmp = displayio.Bitmap(1, 1, len(color_options)) |
| 184 | +color_preview_group = displayio.Group(scale=30 * 2) |
| 185 | + |
| 186 | +# centered horizontally near bottom on screen |
| 187 | +color_preview_group.x = 240 // 2 - 60 // 2 |
| 188 | +color_preview_group.y = 240 - (60 + 2) |
| 189 | + |
| 190 | +color_preview_tilegrid = displayio.TileGrid( |
| 191 | + color_select_preview_bmp, pixel_shader=color_select_palette |
| 192 | +) |
| 193 | +color_preview_group.append(color_preview_tilegrid) |
| 194 | + |
| 195 | +# Create a TileGrid using the Bitmap and Palette |
| 196 | +tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette_mapping) |
| 197 | +patchwork_group = displayio.Group(scale=30) |
| 198 | +patchwork_group.append(tile_grid) |
| 199 | + |
| 200 | +# Create main Group |
| 201 | +group = displayio.Group() |
| 202 | + |
| 203 | +# Add the patchwork to the main |
| 204 | +group.append(patchwork_group) |
| 205 | + |
| 206 | +# Add the Group to the Display |
| 207 | +display.show(group) |
| 208 | + |
| 209 | +cur_color = 0 |
| 210 | + |
| 211 | +prev_b = clue.button_b |
| 212 | +prev_a = clue.button_a |
| 213 | + |
| 214 | +nearby_addresses = ["myself"] |
| 215 | +nearby_colors = [color_options[cur_color]] |
| 216 | + |
| 217 | +make_white() |
| 218 | + |
| 219 | +last_scan_time = -30 |
| 220 | +SCAN_INTERVAL = 30 # seconds |
| 221 | + |
| 222 | +while True: |
| 223 | + now = time.monotonic() |
| 224 | + cur_a = clue.button_a |
| 225 | + cur_b = clue.button_b |
| 226 | + if current_mode == MODE_SHOW_PATCHWORK: |
| 227 | + # a button was pressed |
| 228 | + if cur_a and not prev_a: |
| 229 | + current_mode = MODE_COLOR_SELECT |
| 230 | + |
| 231 | + # insert color select layout |
| 232 | + group.append(color_select_group) |
| 233 | + group.append(color_preview_group) |
| 234 | + |
| 235 | + # is it time to scan? |
| 236 | + if last_scan_time + SCAN_INTERVAL < now: |
| 237 | + ble_scan() |
| 238 | + last_scan_time = now |
| 239 | + print("after scan found {} results".format(len(nearby_colors))) |
| 240 | + # print(nearby_addresses) |
| 241 | + draw_grid() |
| 242 | + |
| 243 | + if clue.proximity >= PROXIMITY_LIMIT: |
| 244 | + clue.white_leds = True |
| 245 | + while clue.proximity >= PROXIMITY_LIMIT: |
| 246 | + r, g, b, w = clue.color |
| 247 | + clue.pixel.fill(((r >> 8) & 0xFF, (g >> 8) & 0xFF, (b >> 8) & 0xFF)) |
| 248 | + change_advertisement( |
| 249 | + ((r & 0xFF00) << 8) + (g & 0xFF00) + ((b >> 8) & 0xFF) |
| 250 | + ) |
| 251 | + time.sleep(0.1) |
| 252 | + clue.white_leds = False |
| 253 | + |
| 254 | + elif current_mode == MODE_COLOR_SELECT: |
| 255 | + # current selection preview |
| 256 | + color_select_preview_bmp[0, 0] = cur_color |
| 257 | + |
| 258 | + # a button was pressed |
| 259 | + if cur_a and not prev_a: |
| 260 | + print("a button") |
| 261 | + # increment currently selected color index |
| 262 | + cur_color += 1 |
| 263 | + # reset to 0 if it's too big |
| 264 | + if cur_color >= len(color_options): |
| 265 | + cur_color = 0 |
| 266 | + print(cur_color) |
| 267 | + # b button was pressed |
| 268 | + if cur_b and not prev_b: |
| 269 | + print("b button") |
| 270 | + # advertise new color selection |
| 271 | + change_advertisement(color_options[cur_color]) |
| 272 | + |
| 273 | + # go back to patchwork mode |
| 274 | + current_mode = MODE_SHOW_PATCHWORK |
| 275 | + # remove color select background |
| 276 | + group.remove(color_select_group) |
| 277 | + group.remove(color_preview_group) |
| 278 | + make_white() |
| 279 | + draw_grid() |
| 280 | + prev_a = cur_a |
| 281 | + prev_b = cur_b |
0 commit comments