Example #1
0
    def __init__(self):
        """
        Initialize menu
        """

        self.lcd = RpiLCDHwd()

        self.lcd.initDisplay()
        self.clearDisplay()

        super(self.__class__, self).__init__()
Example #2
0
    def __init__(self, pin_rs=26, pin_e=19, pins_db=[13, 6, 5, 21], GPIO=None):
        """
        Initialize menu
        """

        self.lcd = RpiLCDHwd(pin_rs, pin_e, pins_db, GPIO)

        self.lcd.initDisplay()
        self.clearDisplay()

        super(self.__class__, self).__init__()
def test_rpilcdmenu_delayMicroseconds_waits_given_microseconds():
    RPi_mock = Mock()
    RPi_mock.GPIO = Mock()

    with patch.dict(sys.modules, {'RPi': RPi_mock, 'RPi.GPIO': Mock()}):
        lcd = RpiLCDHwd(1, 2, [3, 4, 5, 6])

        start_time = datetime.datetime.now()

        lcd.delayMicroseconds(10)

        assert (datetime.datetime.now() - start_time).microseconds >= 10
def test_rpilcdmenu_pulseEnable_is_blinking_pin_e():
    RPi_mock = Mock()
    RPi_mock.GPIO = Mock()
    RPi_mock.GPIO.output = Mock()

    with patch.dict(sys.modules, {'RPi': RPi_mock, 'RPi.GPIO': Mock()}):
        lcd = RpiLCDHwd(1, 2, [3, 4, 5, 6])

        lcd.pulseEnable()

        assert RPi_mock.GPIO.output.mock_calls == [
            call(2, False), call(2, True),
            call(2, False)
        ]
Example #5
0
    def lcd_queue_processor(self):

        self.lcd = RpiLCDHwd(self.pin_rs, self.pin_e, self.pins_db, self.GPIO)
        self.lcd.initDisplay()

        # clear it once in case of existing corruption
        self.clearDisplay()

        # process the queue
        while True:
            items = self.lcd_queue.get()
            func = items[0]
            args = items[1:]
            func(*args)
def test_rpilcdhwd_imports_gpio_and_initializes_provided_gpio_pins_in_bcm_mode(
):
    GPIO_mock = Mock()
    GPIO_mock.setup = Mock()
    GPIO_mock.OUT = 'out'
    GPIO_mock.IN = 'in'
    GPIO_mock.BCM = 'BCM'
    GPIO_mock.setmode = Mock()
    RPi_mock = Mock()
    RPi_mock.GPIO = GPIO_mock

    with patch.dict(sys.modules, {'RPi': RPi_mock, 'RPi.GPIO': Mock()}):
        RpiLCDHwd(1, 2, [3, 4, 5, 6])

        GPIO_mock.setmode.assert_called_once_with(GPIO_mock.BCM)

        setup_calls = [
            call(1, GPIO_mock.OUT),
            call(2, GPIO_mock.OUT),
            call(3, GPIO_mock.OUT),
            call(4, GPIO_mock.OUT),
            call(5, GPIO_mock.OUT),
            call(6, GPIO_mock.OUT)
        ]

        GPIO_mock.setup.assert_has_calls(setup_calls, any_order=True)
def test_rpilcdhwd_initDisplay_configures_proper_lcd_settings():
    RPi_mock = Mock()
    RPi_mock.GPIO = MagicMock()

    with patch.dict(sys.modules, {'RPi': RPi_mock, 'RPi.GPIO': Mock()}):
        lcd = RpiLCDHwd(1, 2, [3, 4, 5, 6])

        lcd.write4bits = Mock()
        lcd.initDisplay()

        assert lcd.write4bits.mock_calls == [
            call(0x33),
            call(0x32),
            call(0x28),
            call(0x0C),
            call(0x06),
            call(0x06),
        ]
def test_rpilcdmenu_write4bits_transfers_data_through_GPIO():
    RPi_mock = Mock()
    RPi_mock.GPIO = Mock()
    RPi_mock.GPIO.output = Mock()

    with patch.dict(sys.modules, {'RPi': RPi_mock, 'RPi.GPIO': Mock()}):
        lcd = RpiLCDHwd(1, 2, [3, 4, 5, 6])

        lcd.delayMicroseconds = Mock()
        lcd.pulseEnable = Mock()

        lcd.write4bits(0x123)
        assert RPi_mock.GPIO.output.mock_calls == [
            call(1, False),
            call(3, False),
            call(4, False),
            call(5, False),
            call(6, False),
            call(6, True),
            call(3, True),
            call(3, False),
            call(4, False),
            call(5, False),
            call(6, False),
            call(3, True)
        ]
Example #9
0
class RpiLCDMenu(BaseMenu):
    def __init__(self, pin_rs=26, pin_e=19, pins_db=[13, 6, 5, 21], GPIO=None):
        """
        Initialize menu
        """

        self.lcd = RpiLCDHwd(pin_rs, pin_e, pins_db, GPIO)

        self.lcd.initDisplay()
        self.clearDisplay()

        super(self.__class__, self).__init__()

    def clearDisplay(self):
        """
        Clear LCD Screen
        """
        self.lcd.write4bits(
            RpiLCDHwd.LCD_CLEARDISPLAY)  # command to clear display
        self.lcd.delayMicroseconds(
            3000
        )  # 3000 microsecond sleep, clearing the display takes a long time

        return self

    def message(self, text):
        """ Send long string to LCD. 17th char wraps to second line"""
        i = 0
        lines = 0

        for char in text:
            if char == '\n':
                self.lcd.write4bits(0xC0)  # next line
                i = 0
                lines += 1
            else:
                self.lcd.write4bits(ord(char), True)
                i = i + 1

            if i == 16:
                self.lcd.write4bits(0xC0)  # last char of the line
            elif lines == 2:
                break

        return self

    def displayTestScreen(self):
        """
        Display test screen to see if your LCD screen is wokring
        """
        self.message('Hum. body 36,6\xDFC\nThis is test')

        return self

    def render(self):
        """
        Render menu
        """
        self.clearDisplay()

        if len(self.items) == 0:
            self.message('Menu is empty')
            return self
        elif len(self.items) <= 2:
            options = (self.current_option == 0 and ">"
                       or " ") + self.items[0].text
            if len(self.items) == 2:
                options += "\n" + (self.current_option == 1 and ">"
                                   or " ") + self.items[1].text
            print(options)
            self.message(options)
            return self

        options = ">" + self.items[self.current_option].text

        if self.current_option + 1 < len(self.items):
            options += "\n " + self.items[self.current_option + 1].text
        else:
            options += "\n " + self.items[0].text

        self.message(options)

        return self
def test_rpilcdhwd_cannot_be_initialized_without_gpio_support():
    with patch.dict(sys.modules, {'RPi.GPIO': None}):
        with pytest.raises(ImportError):
            RpiLCDHwd()
Example #11
0
class RpiLCDMenu(BaseMenu):
    def __init__(self,
                 pin_rs=26,
                 pin_e=19,
                 pins_db=[13, 6, 5, 21],
                 GPIO=None,
                 scrolling_menu=False):
        """
        Initialize menu
        """
        self.lcd_queue = queue.Queue(maxsize=0)
        self.scrolling_menu = scrolling_menu

        self.pin_rs = pin_rs
        self.pin_e = pin_e
        self.pins_db = pins_db
        self.GPIO = GPIO

        # turn-on the worker thread
        threading.Thread(target=self.lcd_queue_processor).start()

        super(self.__class__, self).__init__()

    def clearDisplay(self):
        """
        Clear LCD Screen
        """
        self.lcd.write4bits(
            RpiLCDHwd.LCD_CLEARDISPLAY)  # command to clear display
        self.lcd.delayMicroseconds(
            3000
        )  # 3000 microsecond sleep, clearing the display takes a long time

        return self

    def lcd_render(self, render_text):
        i = 0
        lines = 0

        # return home rather thanclear the display
        self.lcd.write4bits(RpiLCDHwd.LCD_RETURNHOME)

        for char in render_text:
            if char == '\n':
                self.lcd.write4bits(0xC0)  # next line
                i = 0
                lines += 1
            else:
                self.lcd.write4bits(ord(char), True)
                i = i + 1

            if i == 16:
                self.lcd.write4bits(0xC0)  # last char of the line

    def message(self, text, autoscroll=False):
        """ Send long string to LCD. Long single line messages are split and scrolled if autoscroll is set.
        else the are split and cropped."""

        # clear the existing lcd queue
        with self.lcd_queue.mutex:
            self.lcd_queue.queue.clear()

        try:
            splitlines = text.split('\n')

            # process a single line
            if len(splitlines) < 2:
                line1 = splitlines[0]

                # if there's one line and its longer than 16 characters, split it onto line 2
                len1 = len(line1)
                if len1 > 16:
                    #  // will return an integer
                    half = (len1 // 2)
                    # find the next space after half the string and split at the character after it
                    split = line1.find(' ', half) + 1
                    # split it in half
                    line2 = line1[split:]
                    line1 = line1[0:split]
                    # pad out to length of line 1
                    line2 = line2.ljust(len(line1), ' ')
                else:
                    #  line 2 is nothing if line1 is not more than 16 characters
                    line2 = ''

                # recalculate lengths for srcoller
                len1 = len(line1)
                len2 = len(line2)
                final_text = ("%s\n%s" % (line1, line2))

            # process 2 lines
            elif len(splitlines) == 2:
                # set lengths for scroller but other wise leave the text
                len1 = len(splitlines[0])
                len2 = len(splitlines[1])
                # pad out short lines
                line1 = "{:<16}".format(splitlines[0])
                line2 = "{:<16}".format(splitlines[1])
                final_text = ("%s\n%s" % (line1, line2))

            else:
                # TODO process more than 2 lines. Currently they just get cropped.
                len1 = len(splitlines[0])
                len2 = len(splitlines[1])
                # pad out short lines
                line1 = "{:<16}".format(splitlines[0])
                line2 = "{:<16}".format(splitlines[1])
                final_text = ("%s\n%s" % (line1, line2))

            # scroll messages
            if autoscroll == True:
                # add one to the longest length so it scrolls off screen
                if len1 < len2:
                    text_length = len2
                else:
                    text_length = len1

                # render for 16x2
                fixed_text = self.render_16x2(final_text)
                # render the output
                self.lcd_queue.put((self.lcd_render, fixed_text))

                # only scroll if needed
                if text_length > 16:

                    # add one to the longest length so it scrolls off screen
                    text_length = text_length + 1

                    # show the text for one second
                    sleep(1)

                    # scroll the message right to left
                    # start 1 character in as we've already rendered the first character
                    for index in range(1, text_length):
                        # render at 16x2
                        fixed_text = self.render_16x2(final_text, index)

                        # render the output
                        self.lcd_queue.put((self.lcd_render, fixed_text))

                    # scroll the rest of the way
                    for index in range(0, 16):
                        # render at 16x2
                        fixed_text = self.render_16x2_reverse(
                            final_text, index)

                        # render the output
                        self.lcd_queue.put((self.lcd_render, fixed_text))

                    # render for 16x2
                    fixed_text = self.render_16x2(final_text)

                    # render the output
                    self.lcd_queue.put((self.lcd_render, fixed_text))

                return self

            else:
                # just show the text if theres no autoscroll
                fixed_text = self.render_16x2(final_text)

                # render the output
                self.lcd_queue.put((self.lcd_render, fixed_text))

                return self

        except Exception as e:
            print("Autoscroll error: %s" % e)

    def displayTestScreen(self):
        """
        Display test screen to see if your LCD screen is wokring
        """
        self.message('Hum. body 36,6\xDFC\nThis is test')

        return self

    def render(self):
        """
        Render menu
        """
        if len(self.items) == 0:
            self.message('Menu is empty')
            return self
        elif len(self.items) <= 2:
            options = (self.current_option == 0 and ">"
                       or " ") + self.items[0].text
            if len(self.items) == 2:
                options += "\n" + (self.current_option == 1 and ">"
                                   or " ") + self.items[1].text
            # print(options)
            if self.scrolling_menu == True:
                self.message(options, autoscroll=True)
            else:
                self.message(options)
            return self

        options = ">" + self.items[self.current_option].text

        if self.current_option + 1 < len(self.items):
            options += "\n " + self.items[self.current_option + 1].text
        else:
            options += "\n " + self.items[0].text

        if self.scrolling_menu == True:
            self.message(options, autoscroll=True)
        else:
            self.message(options)

        return self

    def render_16x2(self, text, index=0):

        # incoming text will already have been cleaned up and split with a line break
        # by the message function
        try:
            # render incoming text as 16x2 by taking the starting index and adding 16
            # for each line
            lines = text.split('\n')
            line1 = lines[0]
            line2 = lines[1]

            # render from index to 16 characters in
            last_char = index + 16

            # # pad out the text if its less than 16 characters  long
            line1_vfd = "{:<16}".format(line1[index:last_char])
            line2_vfd = "{:<16}".format(line2[index:last_char])

            return ("%s\n%s" % (line1_vfd, line2_vfd))

        except Exception as e:
            print("Render error: %s" % e)

    def render_16x2_reverse(self, text, index=0):

        # incoming text will already have been cleaned up and split with a line break
        # by the message function
        try:
            # render incoming text as 16x2 but right justified. ie add padding to the left.
            # only useful for the reverse scroll
            lines = text.split('\n')
            line1 = lines[0]
            line2 = lines[1]
            # pad out the text if its less than 16 characters long from the left
            line1_vfd = "{:>16}".format(line1[0:index])
            line2_vfd = "{:>16}".format(line2[0:index])

            return ("%s\n%s" % (line1_vfd, line2_vfd))

        except Exception as e:
            print("Render error: %s" % e)

    def lcd_queue_processor(self):

        self.lcd = RpiLCDHwd(self.pin_rs, self.pin_e, self.pins_db, self.GPIO)
        self.lcd.initDisplay()

        # clear it once in case of existing corruption
        self.clearDisplay()

        # process the queue
        while True:
            items = self.lcd_queue.get()
            func = items[0]
            args = items[1:]
            func(*args)