More robust LCD driver
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 13 May 2017 03:45:24 +0000 (20:45 -0700)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 13 May 2017 03:45:24 +0000 (20:45 -0700)
src/py/bbctrl/LCD.py
src/py/lcd/__init__.py

index 1910b4bd24d0785d1155699f7a1aa77b66571630..b21591dd72ed47020171a7189c0ed6371356c71f 100644 (file)
@@ -1,6 +1,7 @@
 import lcd
 import atexit
 import logging
+import tornado.ioloop
 
 
 log = logging.getLogger('LCD')
@@ -9,20 +10,92 @@ log = logging.getLogger('LCD')
 class LCD:
     def __init__(self, ctrl):
         self.ctrl = ctrl
+
+        self.width = 20
+        self.height = 4
+        self.lcd = None
+        self.timeout = None
+        self.clear_next_write = False
+
+        self.clear()
+        self.text('Loading', 6, 1)
+        self.clear_next_write = True
+        self.update_screen()
+
+        # Redraw screen every 5 seconds
+        self.redraw_timer = tornado.ioloop.PeriodicCallback(self._redraw, 5000,
+                                                            self.ctrl.ioloop)
+        self.redraw_timer.start()
+
         atexit.register(self.goodbye)
-        self.connect()
 
 
-    def connect(self):
+    def clear(self):
+        self.screen = [[[' ', False] for x in range(self.width)]
+                       for y in range(self.height)]
+        self.redraw = True
+
+
+    def _trigger_update(self):
+        if self.timeout is None:
+            self.timeout = self.ctrl.ioloop.call_later(0.25, self.update_screen)
+
+
+    def _redraw(self):
+        self.redraw = True
+        self._trigger_update()
+
+
+    def put(self, c, x, y):
+        if self.clear_next_write:
+            self.clear_next_write = False
+            self.clear()
+
+        y += x // self.width
+        x %= self.width
+        y %= self.height
+
+        if self.screen[y][x][0] != c:
+            self.screen[y][x] = [c, True]
+            self._trigger_update()
+
+
+    def text(self, s, x, y):
+        for c in s:
+            self.put(c, x, y)
+            x += 1
+
+
+    def update_screen(self):
+        self.timeout = None
+
         try:
-            self.lcd = None
-            self.lcd = lcd.LCD(self.ctrl.args.lcd_port, self.ctrl.args.lcd_addr)
-            self.lcd.clear()
-            self.lcd.display(1, 'Loading', lcd.JUSTIFY_CENTER)
+            if self.lcd is None:
+                self.lcd = lcd.LCD(self.ctrl.args.lcd_port,
+                                   self.ctrl.args.lcd_addr,
+                                   self.height, self.width)
+
+            cursorX, cursorY = -1, -1
+
+            for y in range(self.height):
+                for x in range(self.width):
+                    cell = self.screen[y][x]
+
+                    if self.redraw or cell[1]:
+                        if cursorX != x or cursorY != y:
+                            self.lcd.goto(x, y)
+                            cursorX, cursorY = x, y
+
+                        self.lcd.put_char(cell[0])
+                        cursorX += 1
+                        cell[1] = False
+
+            self.redraw = False
 
         except IOError as e:
-            log.error('Connect failed, retrying: %s' % e)
-            self.ctrl.ioloop.call_later(1, self.connect)
+            log.error('LCD communication failed, retrying: %s' % e)
+            self.redraw = True
+            self.timeout = self.ctrl.ioloop.call_later(1, self.update_screen)
 
 
     def update(self, msg):
@@ -31,24 +104,31 @@ class LCD:
             state = v.get('x', 'INIT')
             if 'c' in v and state == 'RUNNING': state = v['c']
 
-            self.lcd.text('%-9s' % state, 0, 0)
+            self.text('%-9s' % state, 0, 0)
 
-        if 'xp' in msg: self.lcd.text('% 10.4fX' % msg['xp'], 9, 0)
-        if 'yp' in msg: self.lcd.text('% 10.4fY' % msg['yp'], 9, 1)
-        if 'zp' in msg: self.lcd.text('% 10.4fZ' % msg['zp'], 9, 2)
-        if 'ap' in msg: self.lcd.text('% 10.4fA' % msg['ap'], 9, 3)
-        if 't' in msg:  self.lcd.text('%2uT'     % msg['t'],  6, 1)
-        if 'u' in msg:  self.lcd.text('%s'       % msg['u'],  0, 1)
-        if 'f' in msg:  self.lcd.text('%8uF'     % msg['f'],  0, 2)
-        if 's' in msg:  self.lcd.text('%8dS'     % msg['s'],  0, 3)
+        if 'xp' in msg: self.text('% 10.4fX' % msg['xp'], 9, 0)
+        if 'yp' in msg: self.text('% 10.4fY' % msg['yp'], 9, 1)
+        if 'zp' in msg: self.text('% 10.4fZ' % msg['zp'], 9, 2)
+        if 'ap' in msg: self.text('% 10.4fA' % msg['ap'], 9, 3)
+        if 't' in msg:  self.text('%2uT'     % msg['t'],  6, 1)
+        if 'u' in msg:  self.text('%s'       % msg['u'],  0, 1)
+        if 'f' in msg:  self.text('%8uF'     % msg['f'],  0, 2)
+        if 's' in msg:  self.text('%8dS'     % msg['s'],  0, 3)
 
 
     def goodbye(self):
-        if self.lcd is None: return
+        if self.timeout:
+            self.ctrl.ioloop.remove_timeout(self.timeout)
+            self.timeout = None
 
-        try:
-            self.lcd.clear()
-            self.lcd.display(1, 'Goodbye', lcd.JUSTIFY_CENTER)
+        if self.redraw_timer:
+            self.redraw_timer.stop()
+            self.redraw_timer = None
 
-        except IOError as e:
-            log.error('I2C communication failed: %s' % e)
+        if self.lcd is not None:
+            try:
+                self.lcd.clear()
+                self.lcd.display(1, 'Goodbye', lcd.JUSTIFY_CENTER)
+
+            except IOError as e:
+                log.error('LCD communication failed: %s' % e)
index 64648fec20d1901f3d7e03bfe09c4e5c6eba1d8b..5094eb9779c6b839c6c152803d2a6b7640af24f1 100755 (executable)
@@ -148,11 +148,14 @@ class LCD:
         self.write(LCD_SET_DDRAM_ADDR | (0, 64, 20, 84)[y] + int(x))
 
 
+    def put_char(self, c):
+        self.write(ord(c), REG_SELECT_BIT)
+
+
     def text(self, msg, x = None, y = None):
         if x is not None and y is not None: self.goto(x, y)
 
-        for c in msg:
-            self.write(ord(c), REG_SELECT_BIT)
+        for c in msg: self.put_char(c)
 
 
     def display(self, line, msg, justify = JUSTIFY_LEFT):