1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
#!/usr/bin/env python3
import sys
import pygame
import nxt.locator
from nxt.error import DirectProtocolError
""" example that reads out the memory region used for the display content
("framebuffer") of a USB-connected NXT
useful for those bricks with displays that died of old age (the display is
probably still fine, but the glue on the ribbon cable usually isn't)
this code basically reimplements what
https://bricxcc.sourceforge.net/utilities.html -> "NeXTScreen"
in a less obscure programming language ;-)
"""
PIXEL_SIZE = 6
WINDOW_WIDTH = 100 * PIXEL_SIZE
WINDOW_HEIGHT = (64 + 32) * PIXEL_SIZE # Additional 32 lines for the GUI-buttons
MAIN_WINDOW_SURFACE = None
FPS_CLOCK = pygame.time.Clock()
def NXT_get_display_data(b):
# each "module" has it's own memory region, id, ...
# for t in b.find_modules():
# print(t, hex(t[1]))
mod_display = 0xA0001
# display is WxH = 100x64 pixels
# their memory layout is in eight lines of 100 bytes, with each byte encoding
# a strip of eight vertical pixels
data = b""
pixels = [""] * 64
DisplayOffsetNormal = 119
for i in range(20):
data += b.read_io_map(mod_display, DisplayOffsetNormal + i * 40, 40)[1]
for line in range(8):
for x in range(100):
b = data[line * 100 + x]
for y in range(8):
bitsels = format(
b, "08b"
) # abuse the stringification to get 8 1's and 0's
pixels[line * 8 + y] += bitsels[7 - y]
return pixels
def initialize_pygame():
pygame.init()
main_window_size = (WINDOW_WIDTH, WINDOW_HEIGHT)
global MAIN_WINDOW_SURFACE
MAIN_WINDOW_SURFACE = pygame.display.set_mode(main_window_size)
pygame.display.set_caption("NXT Screen")
# paint the buttons...
rect_return = pygame.Rect(
(50 - 13) * PIXEL_SIZE, (64 + 2) * PIXEL_SIZE, 26 * PIXEL_SIZE, 16 * PIXEL_SIZE
)
pygame.draw.rect(MAIN_WINDOW_SURFACE, pygame.Color("orange"), rect_return)
pygame.draw.polygon(
MAIN_WINDOW_SURFACE,
pygame.Color("antiquewhite3"),
(
((50 - 13 - 30) * PIXEL_SIZE, (64 + 2 + 8) * PIXEL_SIZE),
((50 - 13 - 30 + 26) * PIXEL_SIZE, (64 + 2) * PIXEL_SIZE),
((50 - 13 - 30 + 26) * PIXEL_SIZE, (64 + 2 + 16) * PIXEL_SIZE),
),
)
pygame.draw.polygon(
MAIN_WINDOW_SURFACE,
pygame.Color("antiquewhite3"),
(
((50 + 13 + 30) * PIXEL_SIZE - 1, (64 + 2 + 8) * PIXEL_SIZE),
((50 + 13 + 30 - 26) * PIXEL_SIZE - 1, (64 + 2) * PIXEL_SIZE),
((50 + 13 + 30 - 26) * PIXEL_SIZE - 1, (64 + 2 + 16) * PIXEL_SIZE),
),
)
rect_exit = pygame.Rect(
(50 - 13) * PIXEL_SIZE, (64 + 20) * PIXEL_SIZE, 26 * PIXEL_SIZE, 10 * PIXEL_SIZE
)
pygame.draw.rect(MAIN_WINDOW_SURFACE, pygame.Color("antiquewhite4"), rect_exit)
def draw(pixels):
for y in range(64):
for x in range(100):
p = pixels[y][x]
if p == "0":
color = pygame.Color("grey66")
else:
color = pygame.Color("black")
rect = pygame.Rect(x * PIXEL_SIZE, y * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE)
pygame.draw.rect(MAIN_WINDOW_SURFACE, color, rect)
def main_loop():
b = nxt.locator.find()
print("Found brick:", b.get_device_info()[0])
# And play a recognizable note.
b.play_tone(440, 250)
while True:
for event in pygame.event.get():
# react to titlebar 'X' beeing clicked
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# or the press of the 'Esc' key
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
elif (
event.type == pygame.MOUSEBUTTONDOWN
): # interpret mouse-button clicks on the GUI buttons
if (50 - 13) * PIXEL_SIZE <= event.pos[0] <= (
50 - 13 + 26
) * PIXEL_SIZE and (64 + 2) * PIXEL_SIZE <= event.pos[1] <= (
64 + 2 + 16
) * PIXEL_SIZE:
b.write_io_map(0x40001, 35, bytearray([0x80]))
b.write_io_map(0x40001, 35, bytearray([0x80]))
if (50 - 13 - 30) * PIXEL_SIZE <= event.pos[0] <= (
50 - 13 - 30 + 26
) * PIXEL_SIZE and (64 + 2) * PIXEL_SIZE <= event.pos[1] <= (
64 + 2 + 16
) * PIXEL_SIZE:
b.write_io_map(0x40001, 34, bytearray([0x80]))
b.write_io_map(0x40001, 34, bytearray([0x80]))
if (50 + 13 + 30 - 26) * PIXEL_SIZE <= event.pos[0] <= (
50 + 13 + 30
) * PIXEL_SIZE and (64 + 2) * PIXEL_SIZE <= event.pos[1] <= (
64 + 2 + 16
) * PIXEL_SIZE:
b.write_io_map(0x40001, 33, bytearray([0x80]))
b.write_io_map(0x40001, 33, bytearray([0x80]))
if (50 - 13) * PIXEL_SIZE <= event.pos[0] <= (
50 - 13 + 26
) * PIXEL_SIZE and (64 + 20) * PIXEL_SIZE <= event.pos[1] <= (
64 + 20 + 10
) * PIXEL_SIZE:
try:
b.stop_program()
except DirectProtocolError:
b.write_io_map(0x40001, 32, bytearray([0x80]))
b.write_io_map(0x40001, 32, bytearray([0x80]))
# beep to let the user know that a screen refresh happended
# removed beep as it annoyed me :-)
# b.play_tone(440, 25)
p = NXT_get_display_data(b)
draw(p)
pygame.display.update()
# keep the refresh rate low, to not interfere with the NXP too much
# since usb-commands keep the command processor busy, which might tripp
# up the more timing critical bluetooth handling parts
# in my case 10 fps is still OK when connecting via USB:
FPS_CLOCK.tick(10)
if __name__ == "__main__":
initialize_pygame()
main_loop()
|