Browse Source

pico show battery state

Thomas Buck 1 year ago
parent
commit
272c7e101b
3 changed files with 181 additions and 11 deletions
  1. 3
    0
      camp_pico.py
  2. 3
    0
      mapper.py
  3. 175
    11
      pico.py

+ 3
- 0
camp_pico.py View File

22
     from scroll import ScrollText
22
     from scroll import ScrollText
23
     from splash import SplashScreen
23
     from splash import SplashScreen
24
     from manager import Manager
24
     from manager import Manager
25
+    from pico import PicoBatt
25
 
26
 
26
     url = "http://ubabot.frubar.net"
27
     url = "http://ubabot.frubar.net"
27
 
28
 
64
     m.add(Solid(t, 1.0))
65
     m.add(Solid(t, 1.0))
65
     m.add(d) # HTTP Check, either "success" or "fail"
66
     m.add(d) # HTTP Check, either "success" or "fail"
66
     m.add(Solid(t, 1.0))
67
     m.add(Solid(t, 1.0))
68
+    m.add(PicoBatt(t, 5.0, 5.0))
69
+    m.add(Solid(t, 1.0))
67
 
70
 
68
     m.restart()
71
     m.restart()
69
     t.loop(m.draw)
72
     t.loop(m.draw)

+ 3
- 0
mapper.py View File

31
         if hasattr(self.gui, "matrix"):
31
         if hasattr(self.gui, "matrix"):
32
             self.matrix = self.gui.matrix
32
             self.matrix = self.gui.matrix
33
 
33
 
34
+    def batteryCache(self, refresh = False):
35
+        return self.gui.batteryCache(refresh)
36
+
34
     def loop_start(self):
37
     def loop_start(self):
35
         return self.gui.loop_start()
38
         return self.gui.loop_start()
36
 
39
 

+ 175
- 11
pico.py View File

14
 import hub75
14
 import hub75
15
 from mapper import MapperReduceBrightness
15
 from mapper import MapperReduceBrightness
16
 import time
16
 import time
17
-from machine import Pin
17
+from machine import Pin, ADC
18
+import math
19
+
20
+# https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/examples/interstate75/75W/clock.py
21
+@micropython.native  # noqa: F821
22
+def from_hsv(h, s, v):
23
+    i = math.floor(h * 6.0)
24
+    f = h * 6.0 - i
25
+    v *= 255.0
26
+    p = v * (1.0 - s)
27
+    q = v * (1.0 - f * s)
28
+    t = v * (1.0 - (1.0 - f) * s)
29
+
30
+    i = int(i) % 6
31
+    if i == 0:
32
+        return int(v), int(t), int(p)
33
+    if i == 1:
34
+        return int(q), int(v), int(p)
35
+    if i == 2:
36
+        return int(p), int(v), int(t)
37
+    if i == 3:
38
+        return int(p), int(q), int(v)
39
+    if i == 4:
40
+        return int(t), int(p), int(v)
41
+    if i == 5:
42
+        return int(v), int(p), int(q)
43
+
44
+def batt_to_color(batt):
45
+    h = batt[0] / 100.0 * 0.3333
46
+    r, g, b = from_hsv(h, 1.0, 1.0)
47
+    return r, g, b
18
 
48
 
19
 class PicoMatrix:
49
 class PicoMatrix:
20
     def __init__(self, w = 32, h = 32):
50
     def __init__(self, w = 32, h = 32):
38
 
68
 
39
         self.ledTime = time.time()
69
         self.ledTime = time.time()
40
         self.led = Pin("LED", Pin.OUT)
70
         self.led = Pin("LED", Pin.OUT)
71
+        self.ledRefresh = 0.5
72
+
73
+        self.adc = ADC(26)
74
+        self.battState = None
75
+        self.battTime = time.time()
76
+        self.battRefresh = 10
41
 
77
 
42
         self.loop_start() # initialize with blank image for ScrollText constructor
78
         self.loop_start() # initialize with blank image for ScrollText constructor
43
 
79
 
80
+    def battery(self):
81
+        n = const(10)
82
+        bits = const(10)
83
+        raw = 0
84
+        for i in range(0, n):
85
+            raw += self.adc.read_u16() >> (16 - bits)
86
+            time.sleep(0.1 / n)
87
+        raw /= n
88
+
89
+        v_adc_ref = const(3.3) # V
90
+        v_adc = (raw / ((1 << bits) - 1)) * v_adc_ref
91
+
92
+        r1 = const(27.0) # kOhm
93
+        r2 = const(5.6) # kOhm
94
+        v_bat_uncal = (v_adc * (r1 + r2)) / r2
95
+
96
+        # Calibration
97
+        # TODO supports only 2 points
98
+        cal_pts = [
99
+            #  adc,   bat
100
+            (13.65, 15.46),
101
+            (13.60, 15.34),
102
+            # TODO better values
103
+        ]
104
+
105
+        # https://www.ti.com/europe/downloads/f2810_12_calibration_10.pdf
106
+        gain = (cal_pts[0][1] - cal_pts[1][1]) / (cal_pts[0][0] - cal_pts[1][0])
107
+        offset = cal_pts[1][1] - cal_pts[1][0] * gain
108
+        v_bat = v_bat_uncal * gain + offset
109
+
110
+        # TODO auto-detect cell count
111
+        n_lipo = const(4) # 4S
112
+        v_cell = v_bat / n_lipo
113
+
114
+        v_cell_min = const(3.25)
115
+        v_cell_max = const(4.2)
116
+        p_cell = (v_cell - v_cell_min) / (v_cell_max - v_cell_min) * 100.0
117
+        p_cell = max(min(p_cell, 100.0), 0.0)
118
+
119
+        ret = (p_cell, v_cell, v_bat)
120
+
121
+        r, g, b = batt_to_color(ret)
122
+        self.matrix.set_led(r, g, b)
123
+
124
+        return ret
125
+
126
+    def batteryCache(self, refresh = False):
127
+        now = time.time()
128
+        if (self.battState == None) or ((now - self.battTime) >= self.battRefresh) or (now < self.battTime) or refresh:
129
+            self.battTime = now
130
+            self.battState = self.battery()
131
+        return self.battState
132
+
133
+    def heartbeat(self):
134
+        now = time.time()
135
+        if ((now - self.ledTime) >= self.ledRefresh) or (now < self.ledTime):
136
+            self.ledTime = now
137
+            self.led.toggle()
138
+
44
     def loop_start(self):
139
     def loop_start(self):
45
         self.matrix.display.set_pen(self.black)
140
         self.matrix.display.set_pen(self.black)
46
         self.matrix.display.clear()
141
         self.matrix.display.clear()
51
     def loop_end(self):
146
     def loop_end(self):
52
         self.matrix.update()
147
         self.matrix.update()
53
 
148
 
149
+        # LED heartbeat blink
150
+        self.heartbeat()
151
+
152
+        # update battery if necessary
153
+        self.batteryCache(False)
154
+
54
     def loop(self, func = None):
155
     def loop(self, func = None):
55
         while True:
156
         while True:
56
             if self.loop_start():
157
             if self.loop_start():
61
 
162
 
62
             self.loop_end()
163
             self.loop_end()
63
 
164
 
64
-            now = time.time()
65
-            if ((now - self.ledTime) >= 0.5) or (now < self.ledTime):
66
-                self.ledTime = now
67
-                self.led.toggle()
68
-
69
         self.matrix.stop()
165
         self.matrix.stop()
70
 
166
 
71
     def set_pixel(self, x, y, color):
167
     def set_pixel(self, x, y, color):
86
 
182
 
87
     def text(self, s, f, offset = 0, earlyAbort = True, yOff = 0, compat = True):
183
     def text(self, s, f, offset = 0, earlyAbort = True, yOff = 0, compat = True):
88
         if not earlyAbort:
184
         if not earlyAbort:
185
+            self.gui.matrix.display.set_font(f)
89
             return self.gui.matrix.display.measure_text(s, scale=1)
186
             return self.gui.matrix.display.measure_text(s, scale=1)
90
 
187
 
91
         color = self.fg
188
         color = self.fg
116
 
213
 
117
         self.gui.matrix.display.text(s, x, y, scale=1)
214
         self.gui.matrix.display.text(s, x, y, scale=1)
118
 
215
 
216
+class PicoBatt:
217
+    def __init__(self, g, ti = 5.0, tt = 5.0):
218
+        self.gui = g
219
+        self.timeImage = ti
220
+        self.timeText = tt
221
+        self.text = PicoText(self.gui)
222
+        self.restart()
223
+
224
+    def restart(self):
225
+        self.start = time.time()
226
+
227
+    def finished(self):
228
+        now = time.time()
229
+        return ((now - self.start) >= (self.timeText + self.timeImage)) or (now < self.start)
230
+
231
+    def drawText(self, refresh = False):
232
+        batt = self.gui.batteryCache(refresh)
233
+        c = batt_to_color(batt)
234
+
235
+        self.text.fg = (255, 255, 255)
236
+        self.text.text(                  "Batt:", "bitmap8", 0, True, 8 * 0, False)
237
+
238
+        self.text.fg = c
239
+        self.text.text("{:.2f}%".format(batt[0]), "bitmap8", 0, True, 8 * 1, False)
240
+        self.text.text("{:.2f}V".format(batt[1]), "bitmap8", 0, True, 8 * 2, False)
241
+        self.text.text("{:.2f}V".format(batt[2]), "bitmap8", 0, True, 8 * 3, False)
242
+
243
+    def drawImage(self, refresh = False):
244
+        x_off = const(1)
245
+        y_off = const(16)
246
+        nub_w = const(3)
247
+        nub_h = const(6)
248
+
249
+        w = self.gui.width - x_off * 2
250
+        h = self.gui.height - y_off
251
+
252
+        batt = self.gui.batteryCache(refresh)
253
+        c = batt_to_color(batt)
254
+        fill_w = int(batt[0] / 100.0 * (w - nub_w))
255
+
256
+        s = "{:.0f}%".format(batt[0])
257
+        s_w = self.text.text(s, "bitmap14_outline", 0, False)
258
+        self.text.fg = (255, 255, 255)
259
+        self.text.text(s, "bitmap14_outline", int((self.gui.width - s_w) / 2), True, 1, False)
260
+
261
+        for x in range(0, w - nub_w):
262
+            for y in range(0, h):
263
+                if (x == 0) or (x == (w - nub_w - 1)) or (y == 0) or (y == (h - 1)) or (x < fill_w):
264
+                    self.gui.set_pixel(x + x_off, y + y_off, c)
265
+
266
+        for x in range(0, nub_w):
267
+            for y in range(0, nub_h):
268
+                self.gui.set_pixel(x + x_off + w - nub_w, y + y_off + int((h - nub_h) / 2), c)
269
+
270
+    def draw(self, refresh = False):
271
+        now = time.time()
272
+        if (now - self.start) < self.timeImage:
273
+            self.drawImage(refresh)
274
+        else:
275
+            self.drawText(refresh)
276
+
119
 if __name__ == "__main__":
277
 if __name__ == "__main__":
120
     import time
278
     import time
121
 
279
 
122
     t = PicoMatrix(32, 32)
280
     t = PicoMatrix(32, 32)
123
     s = PicoText(t)
281
     s = PicoText(t)
282
+    b = PicoBatt(t, 10.0, 10.0)
124
 
283
 
125
     start = time.time()
284
     start = time.time()
126
     i = 0
285
     i = 0
127
     def helper():
286
     def helper():
128
-        global s, start, i
287
+        global s, start, i, b
129
 
288
 
130
         now = time.time()
289
         now = time.time()
131
-        if ((now - start) > 2.0) or (now < start):
290
+        if ((now - start) > 5.0) or (now < start):
132
             start = now
291
             start = now
133
-            i = (i + 1) % 2
134
-
135
-        if i == 0:
292
+            i = (i + 1) % 6
293
+            if i == 0:
294
+                b.restart()
295
+
296
+        if i < 4:
297
+            b.draw(True)
298
+            time.sleep(1.0)
299
+        elif i == 4:
136
             s.text("Abgj6", "bitmap6", 0, True, 0, False)
300
             s.text("Abgj6", "bitmap6", 0, True, 0, False)
137
             s.text("Abdgj8", "bitmap8", 0, True, 6 + 2, False)
301
             s.text("Abdgj8", "bitmap8", 0, True, 6 + 2, False)
138
             s.text("Ag14", "bitmap14_outline", 0, True, 6 + 2 + 8 + 1, False)
302
             s.text("Ag14", "bitmap14_outline", 0, True, 6 + 2 + 8 + 1, False)

Loading…
Cancel
Save