File: nxt-screen.py

package info (click to toggle)
nxt-python 3.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 812 kB
  • sloc: python: 6,857; xml: 22; makefile: 20; sh: 4
file content (173 lines) | stat: -rwxr-xr-x 6,260 bytes parent folder | download | duplicates (2)
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()