def test_display(bits_per_pixel, bgr): bytes_per_pixel = bits_per_pixel // 8 with open(get_reference_file(f"fb_{bits_per_pixel}bpp.raw"), "rb") as fp: reference = fp.read() if bgr: reference = swap_red_and_blue(reference, step=bytes_per_pixel) with patch("builtins.open", multi_mock_open(SCREEN_RES, str(bits_per_pixel), None)) as fake_open: device = linux_framebuffer("/dev/fb1", framebuffer=full_frame(), bgr=bgr) fake_open.assert_has_calls([call("/dev/fb1", "wb")]) fake_open.reset_mock() with canvas(device, dither=True) as draw: draw.rectangle((0, 0, 64, 32), fill="red") draw.rectangle((64, 0, 128, 32), fill="yellow") draw.rectangle((0, 32, 64, 64), fill="orange") draw.rectangle((64, 32, 128, 64), fill="white") fake_open.return_value.seek.assert_has_calls( [call(n * WIDTH * bytes_per_pixel) for n in range(HEIGHT)]) fake_open.return_value.write.assert_has_calls([ call(reference[n:n + (WIDTH * bytes_per_pixel)]) for n in range(0, len(reference), WIDTH * bytes_per_pixel) ]) fake_open.return_value.flush.assert_called_once()
def test_init_8bitmode(): """ Test initialization of display using 4 bit mode """ interface._bitmode = 8 hd44780(interface, gpio=gpio, framebuffer=full_frame()) to_8 = \ [call(0x30)] * 3 fs = [CONST.FUNCTIONSET | CONST.DL8 | CONST.LINES2] calls = \ to_8 + \ [call(*fs)] + \ [call(*[CONST.DISPLAYOFF])] + \ [call(*[CONST.ENTRY])] + \ [call(*[CONST.DISPLAYON])] + \ [call(*[CONST.DDRAMADDR])] + \ [call(*[CONST.DDRAMADDR | CONST.LINES[1]])] + \ [call(*[CONST.CLEAR])] interface.command.assert_has_calls(calls) # Data to clear the screen calls = \ [call([0x20] * 16)] + \ [call([0x20] * 16)] interface.data.assert_has_calls(calls)
def test_monochrome_display(): """ SSD1322 OLED screen can draw and display a monochrome image. """ device = ssd1322(serial, mode="1", framebuffer=full_frame()) serial.reset_mock() recordings = [] def data(data): recordings.append({'data': data}) def command(*cmd): recordings.append({'command': list(cmd)}) serial.command.side_effect = command serial.data.side_effect = data # Use the same drawing primitives as the demo with canvas(device) as draw: primitives(device, draw) assert serial.data.called assert serial.command.called # To regenerate test data, uncomment the following (remember not to commit though) # ================================================================================ # from baseline_data import save_reference_data # save_reference_data("demo_ssd1322_monochrome", recordings) assert recordings == get_reference_data('demo_ssd1322_monochrome')
def test_init_8bitmode(): interface._bitmode = 8 ws0010(interface, framebuffer=full_frame()) to_4 = \ [call(0, 0, 3, 57)] calls = \ to_4 + \ [call(DISPLAYOFF)] + \ [call(POWEROFF)] + \ [call(ENTRY)] + \ [call(POWERON | GRAPHIC)] + \ [call(DISPLAYON)] + \ [call(*[DDRAMADDR, CGRAMADDR])] + \ [call(*[DDRAMADDR, CGRAMADDR + 1])] interface.command.assert_has_calls(calls) # Data to clear the screen calls = \ [call(bytearray([0] * 100))] + \ [call(bytearray([0] * 100))] interface.data.assert_has_calls(calls)
def test_display(): device = st7735(serial, gpio=Mock(), framebuffer=full_frame()) serial.reset_mock() recordings = [] def data(data): recordings.append({'data': data}) def command(*cmd): recordings.append({'command': list(cmd)}) serial.command.side_effect = command serial.data.side_effect = data # Use the same drawing primitives as the demo with canvas(device) as draw: primitives(device, draw) assert serial.data.called assert serial.command.called # To regenerate test data, uncomment the following (remember not to commit though) # ================================================================================ # from baseline_data import save_reference_data # save_reference_data("demo_st7735", recordings) assert recordings == get_reference_data('demo_st7735')
def test_init_4bitmode(): """ Test initialization of display using 4 bit mode """ hd44780(interface, gpio=gpio, framebuffer=full_frame()) to_8 = \ [call(0x3), call(0x3), call(0x3, 0x3)] * 3 to_4 = \ [call(0x3), call(0x3), call(0x3, 0x02)] fs = [CONST.FUNCTIONSET | CONST.DL4 | CONST.LINES2] calls = \ to_8 + \ to_4 + \ [call(*bytes_to_nibbles(fs))] + \ [call(*bytes_to_nibbles([CONST.DISPLAYOFF]))] + \ [call(*bytes_to_nibbles([CONST.ENTRY]))] + \ [call(*bytes_to_nibbles([CONST.DISPLAYON]))] + \ [call(*bytes_to_nibbles([CONST.DDRAMADDR]))] + \ [call(*bytes_to_nibbles([CONST.DDRAMADDR | CONST.LINES[1]]))] + \ [call(*bytes_to_nibbles([CONST.CLEAR]))] interface.command.assert_has_calls(calls) # Data to clear the screen calls = \ [call(bytes_to_nibbles([0x20] * 16))] + \ [call(bytes_to_nibbles([0x20] * 16))] interface.data.assert_has_calls(calls)
def test_show(): """ SSD1331 OLED screen content can be displayed. """ device = ssd1331(serial, framebuffer=full_frame()) serial.reset_mock() device.show() serial.command.assert_called_once_with(175)
def test_hide(): """ SSD1331 OLED screen content can be hidden. """ device = ssd1331(serial, framebuffer=full_frame()) serial.reset_mock() device.hide() serial.command.assert_called_once_with(174)
def assert_invalid_dimensions(deviceType, serial_interface, width, height): """ Assert an invalid resolution raises a :py:class:`luma.core.error.DeviceDisplayModeError`. """ with pytest.raises(luma.core.error.DeviceDisplayModeError) as ex: deviceType(serial_interface, width=width, height=height, framebuffer=full_frame()) assert f"Unsupported display mode: {width} x {height}" in str(ex.value)
def test_unsupported_display_mode(): """ An exception is thrown if an unsupported display mode is requested """ import luma.core try: hd44780(interface, width=12, height=3, gpio=gpio, framebuffer=full_frame()) except luma.core.error.DeviceDisplayModeError as ex: assert str(ex) == "Unsupported display mode: 12 x 3"
def test_i2c_does_not_support_backlight(): """ An exception is thrown if supplied interface does not support a backlight """ import luma.core interface = Mock(spec_set=luma.core.interface.serial.i2c) flag = False try: hd44780(interface, gpio=gpio, backpack_pin=3, framebuffer=full_frame()) except luma.core.error.UnsupportedPlatform as ex: assert str(ex) == "This I2C interface does not support a backlight" flag = True assert flag, "Expected exception but none occured"
def test_16bit_rgb_packing(bit, expected_16_bit_color): """ Checks that 8 bit red component is packed into first 5 bits Checks that 8 bit green component is packed into next 6 bits Checks that 8 bit blue component is packed into remaining 5 bits """ device = ssd1331(serial, framebuffer=full_frame()) serial.reset_mock() rgb_color = (2**bit, ) * 3 expected = expected_16_bit_color * device.width * device.height with canvas(device) as draw: draw.rectangle(device.bounding_box, outline=rgb_color, fill=rgb_color) serial.data.assert_called_once_with(expected)
def test_init_256x64(): """ SSD1362 OLED with a 256 x 64 resolution works correctly. """ ssd1362(serial, framebuffer=full_frame()) serial.command.assert_has_calls([ call(171, 1, 173, 158, 21, 0, 127, 117, 0, 63, 160, 67, 161, 0, 162, 0, 164, 168, 63, 177, 17, 179, 240, 185, 188, 4, 190, 5), call(129, 127), call(21, 0, 127, 117, 0, 63), call(175) ]) # Next 4096 are all data: zero's to clear the RAM # (4096 = 128 * 64 / 2) serial.data.assert_called_once_with([0] * (256 * 64 // 2))
def test_init_128x128(): """ SSD1327 OLED with a 128 x 128 resolution works correctly. """ ssd1327(serial, framebuffer=full_frame()) serial.command.assert_has_calls([ call(174, 160, 83, 161, 0, 162, 0, 164, 168, 127), call(184, 1, 17, 34, 50, 67, 84, 101, 118), call(179, 0, 171, 1, 177, 241, 188, 8, 190, 7, 213, 98, 182, 15), call(129, 127), call(21, 0, 63, 117, 0, 127), call(175) ]) # Next 4096 are all data: zero's to clear the RAM # (4096 = 128 * 128 / 2) serial.data.assert_called_once_with([0] * (128 * 128 // 2))
def test_init_128x64(): """ SSD1325 OLED with a 128 x 64 resolution works correctly. """ ssd1325(serial, framebuffer=full_frame()) serial.command.assert_has_calls([ call(174, 179, 242, 168, 63, 162, 76, 161, 0, 173, 2, 160, 80, 134, 184, 1, 17, 34, 50, 67, 84, 101, 118, 178, 81, 177, 85, 180, 3, 176, 40, 188, 1, 190, 0, 191, 2, 164), call(129, 127), call(21, 0, 63, 117, 0, 63), call(175) ]) # Next 4096 are all data: zero's to clear the RAM # (4096 = 128 * 64 / 2) serial.data.assert_called_once_with([0] * (128 * 64 // 2))
def test_get_font(): """ Test get font capability by requesting two fonts and printing a single character from each that will be different between the two fonts """ device = hd44780(interface, bitmode=8, gpio=gpio, framebuffer=full_frame()) img = Image.new('1', (10, 8), 0) a00 = device.get_font(0) a02 = device.get_font(1) drw = ImageDraw.Draw(img) assert a00.getsize('\u00E0') == (5, 8) drw.text((0, 0), '\u00E0', font=a00, fill='white') drw.text((5, 0), '\u00E0', font=a02, fill='white') assert img.tobytes() == \ b'\x02\x00\x01\x00H\x00\xab\x80\x90@\x93\xc0l@\x03\xc0'
def test_get_font(): interface._bitmode = 8 device = ws0010(interface, framebuffer=full_frame()) img = Image.new('1', (11, 8), 0) device.font.current = 0 f0 = device.font.current f3 = device.get_font('FT11') drw = ImageDraw.Draw(img) assert f0.getsize('\u00E0') == (5, 8) assert f0.getsize('A\u00E0') == (11, 8) drw.text((0, 0), '\u00E0', font=f0, fill='white') # Font 0: α drw.text((6, 0), '\u00E0', font=f3, fill='white') # Font 3: à # Compare to image containing αà assert img.tobytes() == \ b'\x01\x00\x00\x80I\xc0\xa8 \x91\xe0\x92 i\xe0\x00\x00'
def test_display(): interface._bitmode = 4 d = ws0010(interface, framebuffer=full_frame()) interface.reset_mock() # Use canvas to create an all white screen with canvas(d) as drw: drw.rectangle((0, 0, 99, 15), fill='white', outline='white') line1 = [ call.command(*bytes_to_nibbles([DDRAMADDR, CGRAMADDR])), call.data([0x0F] * 200) ] line2 = [ call.command(*bytes_to_nibbles([DDRAMADDR, CGRAMADDR + 1])), call.data([0x0F] * 200) ] interface.assert_has_calls(line1 + line2)
def test_full_frame(): framebuffer = full_frame(device) assert framebuffer.redraw_required(im1) pix1 = list(framebuffer.getdata()) assert len(pix1) == 16 assert pix1 == [0xFF] * 4 + [0x00] * 12 assert framebuffer.bounding_box == (0, 0, 4, 4) assert framebuffer.inflate_bbox() == (0, 0, 4, 4) assert framebuffer.redraw_required(im2) pix2 = list(framebuffer.getdata()) assert len(pix2) == 16 assert pix2 == [0xFF] * 4 + [0xFF, 0x00, 0x00, 0x00] * 3 assert framebuffer.bounding_box == (0, 0, 4, 4) assert framebuffer.redraw_required(im2) pix3 = list(framebuffer.getdata()) assert len(pix3) == 16 assert pix3 == [0xFF] * 4 + [0xFF, 0x00, 0x00, 0x00] * 3 assert framebuffer.bounding_box == (0, 0, 4, 4)
def test_init_128x128(): recordings = [] def data(data): recordings.append({'data': data}) def command(*cmd): recordings.append({'command': list(cmd)}) serial.command.side_effect = command serial.data.side_effect = data st7735(serial, gpio=Mock(), width=128, height=128, framebuffer=full_frame()) assert serial.data.called assert serial.command.called assert recordings == [ {'command': [1]}, {'command': [17]}, {'command': [177]}, {'data': [1, 44, 45]}, {'command': [178]}, {'data': [1, 44, 45]}, {'command': [179]}, {'data': [1, 44, 45, 1, 44, 45]}, {'command': [180]}, {'data': [7]}, {'command': [192]}, {'data': [162, 2, 132]}, {'command': [193]}, {'data': [197]}, {'command': [194]}, {'data': [10, 0]}, {'command': [195]}, {'data': [138, 42]}, {'command': [196]}, {'data': [138, 238]}, {'command': [197]}, {'data': [14]}, {'command': [54]}, {'data': [96]}, {'command': [32]}, {'command': [58]}, {'data': [6]}, {'command': [19]}, {'command': [224]}, {'data': [15, 26, 15, 24, 47, 40, 32, 34, 31, 27, 35, 55, 0, 7, 2, 16]}, {'command': [225]}, {'data': [15, 27, 15, 23, 51, 44, 41, 46, 48, 48, 57, 63, 0, 7, 3, 16]}, {'command': [42]}, {'data': [0, 0, 0, 127]}, {'command': [43]}, {'data': [0, 0, 0, 127]}, {'command': [44]}, {'data': [0] * (128 * 128 * 3)}, {'command': [41]} ]
def test_i2c_backlight(): """ Test of i2c_backlight """ def _mask(pin): """ Return a mask that contains a 1 in the pin position """ return 1 << pin interface = Mock(unsafe=True, _bitmode=4, _backlight_enabled=0, _mask=_mask) hd44780(interface, bitmode=8, backpack_pin=3, gpio=gpio, framebuffer=full_frame()) assert interface._backlight_enabled == 8
def test_init_96x64(): """ SSD1331 OLED with a 96 x 64 resolution works correctly. """ ssd1331(serial, framebuffer=full_frame()) serial.command.assert_has_calls([ # Initial burst are initialization commands call(174, 160, 114, 161, 0, 162, 0, 164, 168, 63, 173, 142, 176, 11, 177, 116, 179, 208, 138, 128, 139, 128, 140, 128, 187, 62, 190, 62, 135, 15), # set contrast call(129, 255, 130, 255, 131, 255), # reset the display call(21, 0, 95, 117, 0, 63), # called last, is a command to show the screen call(175) ]) # Next 12288 are all data: zero's to clear the RAM # (12288 = 96 * 64 * 2) serial.data.assert_called_once_with([0] * 96 * 64 * 2)
def test_display(): """ SSD1331 OLED screen can draw and display an image. """ device = ssd1331(serial, framebuffer=full_frame()) serial.reset_mock() # Use the same drawing primitives as the demo with canvas(device) as draw: primitives(device, draw) # Initial command to reset the display serial.command.assert_called_once_with(21, 0, 95, 117, 0, 63) # To regenerate test data, uncomment the following (remember not to commit though) # ================================================================================ # from baseline_data import save_reference_data # save_reference_data("demo_ssd1331", serial.data.call_args.args[0]) # Next 12288 bytes are data representing the drawn image serial.data.assert_called_once_with(get_reference_data('demo_ssd1331'))
def test_init_320x480(): recordings = [] def data(data): recordings.append({'data': data}) def command(*cmd): recordings.append({'command': list(cmd)}) serial.command.side_effect = command serial.data.side_effect = data ili9486(serial, gpio=Mock(), framebuffer=full_frame()) assert serial.data.called assert serial.command.called # This set of expected results include the padding bytes that # appear necessary with Waveshare's ili9486 implementation. assert recordings == [ {'command': [0xb0]}, {'data': [0x00, 0x00]}, {'command': [0x11]}, {'command': [0x3a]}, {'data': [0x00, 0x66]}, {'command': [0x21]}, {'command': [0xc0]}, {'data': [0x00, 0x09, 0x00, 0x09]}, {'command': [0xc1]}, {'data': [0x00, 0x41, 0x00, 0x00]}, {'command': [0xc2]}, {'data': [0x00, 0x33]}, {'command': [0xc5]}, {'data': [0x00, 0x00, 0x00, 0x36]}, {'command': [0x36]}, {'data': [0x00, 0x08]}, {'command': [0xb6]}, {'data': [0x00, 0x00, 0x00, 0x42, 0x00, 0x3b]}, {'command': [0x13]}, {'command': [0x34]}, {'command': [0x38]}, {'command': [0x11]}, {'command': [0x2a]}, {'data': [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x3f]}, {'command': [0x2b]}, {'data': [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xdf]}, {'command': [0x2c]}, {'data': bytearray([0x00] * (320 * 480 * 3))}, {'command': [0x29]}, ]
def test_init_240x240(): recordings = [] def data(data): recordings.extend(data) def command(*cmd): recordings.extend(['command', list(cmd)[0], 'data', *list(cmd)[1:]]) serial.command.side_effect = command serial.data.side_effect = data st7789(serial, gpio=Mock(), framebuffer=full_frame()) assert serial.data.called assert serial.command.called assert recordings == [ 'command', 54, 'data', 112, 'command', 58, 'data', 6, 'command', 178, 'data', 12, 12, 0, 51, 51, 'command', 183, 'data', 53, 'command', 187, 'data', 25, 'command', 192, 'data', 44, 'command', 194, 'data', 1, 'command', 195, 'data', 18, 'command', 196, 'data', 32, 'command', 198, 'data', 15, 'command', 208, 'data', 164, 161, 'command', 224, 'data', 208, 4, 13, 17, 19, 43, 63, 84, 76, 24, 13, 11, 31, 35, 'command', 225, 'data', 208, 4, 12, 17, 19, 44, 63, 68, 81, 47, 31, 31, 32, 35, 'command', 33, 'data', 'command', 17, 'data', 'command', 41, 'data', 'command', 42, 'data', 0, 0, 0, 239, 'command', 43, 'data', 0, 0, 0, 239, 'command', 44, 'data', *([0] * (240 * 240 * 3)), 'command', 41, 'data' ]
def test_display(): """ Test the display with a line of text and a rectangle to demonstrate correct functioning of the auto-create feature """ device = hd44780(interface, bitmode=8, gpio=gpio, framebuffer=full_frame()) interface.reset_mock() # Use canvas to create a screen worth of data with canvas(device) as drw: # Include unprintable character to show it gets ignored size = device.font.getsize('This is a test\uFFFF') drw.text(((80 - size[0]) // 2, 0), 'This is a test\uFFFF', font=device.font, fill='white') drw.rectangle((10, 10, 69, 14), fill='black', outline='white') drw.rectangle((10, 10, 49, 14), fill='white', outline='white') # Send DDRAMADDR and ascii for the line of text line1 = [call.command(0x80)] + \ [call.data([0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20])] # Create custom characters for the scrollbar custom = [call.command(0x40), call.data([0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00])] + \ [call.command(0x48), call.data([0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x00])] + \ [call.command(0x50), call.data([0x00, 0x00, 0x1f, 0x01, 0x01, 0x01, 0x1f, 0x00])] # Print the resulting custom characters to form the image of the scrollbar line2 = [ call.command(0xc0), call.data([ 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x20, 0x20 ]) ] interface.assert_has_calls(line1 + custom + line2)
def test_init_4bitmode(): ws0010(interface, framebuffer=full_frame()) to_4 = \ [call(0, 0, 0, 0, 0, 2, 2, 9)] calls = \ to_4 + \ [call(*bytes_to_nibbles([DISPLAYOFF]))] + \ [call(*bytes_to_nibbles([POWEROFF]))] + \ [call(*bytes_to_nibbles([ENTRY]))] + \ [call(*bytes_to_nibbles([POWERON | GRAPHIC]))] + \ [call(*bytes_to_nibbles([DISPLAYON]))] + \ [call(*bytes_to_nibbles([DDRAMADDR, CGRAMADDR]))] + \ [call(*bytes_to_nibbles([DDRAMADDR, CGRAMADDR + 1]))] interface.command.assert_has_calls(calls) # Data to clear the screen calls = \ [call(bytes_to_nibbles([0] * 100))] + \ [call(bytes_to_nibbles([0] * 100))] interface.data.assert_has_calls(calls)
def test_no_contrast(): """ HD44780 should ignore requests to change contrast """ device = hd44780(interface, bitmode=8, gpio=gpio, framebuffer=full_frame()) device.contrast(100)
def test_offsets(): recordings = [] def data(data): recordings.append({'data': data}) def command(*cmd): recordings.append({'command': list(cmd)}) serial.command.side_effect = command serial.data.side_effect = data ili9341(serial, gpio=Mock(), width=240, height=240, h_offset=2, v_offset=1, framebuffer=full_frame()) assert serial.data.called assert serial.command.called assert recordings == [ { 'command': [0xef] }, { 'data': [0x03, 0x80, 0x02] }, { 'command': [0xcf] }, { 'data': [0x00, 0xc1, 0x30] }, { 'command': [0xed] }, { 'data': [0x64, 0x03, 0x12, 0x81] }, { 'command': [0xe8] }, { 'data': [0x85, 0x00, 0x78] }, { 'command': [0xcb] }, { 'data': [0x39, 0x2c, 0x00, 0x34, 0x02] }, { 'command': [0xf7] }, { 'data': [0x20] }, { 'command': [0xea] }, { 'data': [0x00, 0x00] }, { 'command': [0xc0] }, { 'data': [0x23] }, { 'command': [0xc1] }, { 'data': [0x10] }, { 'command': [0xc5] }, { 'data': [0x3e, 0x28] }, { 'command': [0xc7] }, { 'data': [0x86] }, { 'command': [0x36] }, { 'data': [0x28] }, { 'command': [0x3a] }, { 'data': [0x46] }, { 'command': [0xb1] }, { 'data': [0x00, 0x18] }, { 'command': [0xb6] }, { 'data': [0x08, 0x82, 0x27] }, { 'command': [0xf2] }, { 'data': [0x00] }, { 'command': [0x26] }, { 'data': [0x01] }, { 'command': [0xe0] }, { 'data': [ 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00 ] }, { 'command': [0xe1] }, { 'data': [ 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f ] }, { 'command': [0x11] }, { 'command': [0x2A] }, { 'data': [0x00, 0x02, 0x00, 0xef + 0x02] }, { 'command': [0x2B] }, { 'data': [0x00, 0x01, 0x00, 0xef + 0x01] }, { 'command': [0x2C] }, { 'data': bytearray([0x00] * (240 * 240 * 3)) }, { 'command': [0x29] }, ]
def test_cleanup(): with patch("builtins.open", multi_mock_open(SCREEN_RES, BITS_PER_PIXEL, None)) as fake_open: device = linux_framebuffer("/dev/fb1", framebuffer=full_frame()) device.cleanup() fake_open.return_value.close.assert_called_once()