forked from MicroPythonOS/MicroPythonOS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_battery_voltage.py
More file actions
456 lines (349 loc) · 14.9 KB
/
test_battery_voltage.py
File metadata and controls
456 lines (349 loc) · 14.9 KB
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
"""
Unit tests for mpos.battery_manager.BatteryManager class.
Tests ADC1/ADC2 detection, caching, WiFi coordination, and voltage calculations.
"""
import unittest
import sys
# Add parent directory to path for imports
sys.path.insert(0, '../internal_filesystem')
# Mock modules before importing BatteryManager
class MockADC:
"""Mock ADC for testing."""
ATTN_11DB = 3
def __init__(self, pin):
self.pin = pin
self._atten = None
self._read_value = 2048 # Default mid-range value
def atten(self, value):
self._atten = value
def read(self):
return self._read_value
def set_read_value(self, value):
"""Test helper to set ADC reading."""
self._read_value = value
class MockPin:
"""Mock Pin for testing."""
def __init__(self, pin_num):
self.pin_num = pin_num
class MockMachine:
"""Mock machine module."""
ADC = MockADC
Pin = MockPin
class MockWifiService:
"""Mock WifiService for testing."""
wifi_busy = False
_connected = False
_temporarily_disabled = False
@classmethod
def is_connected(cls):
return cls._connected
@classmethod
def disconnect(cls):
cls._connected = False
@classmethod
def temporarily_disable(cls):
"""Temporarily disable WiFi and return whether it was connected."""
if cls.wifi_busy:
raise RuntimeError("Cannot disable WiFi: WifiService is already busy")
was_connected = cls._connected
cls.wifi_busy = True
cls._connected = False
cls._temporarily_disabled = True
return was_connected
@classmethod
def temporarily_enable(cls, was_connected):
"""Re-enable WiFi and reconnect if it was connected before."""
cls.wifi_busy = False
cls._temporarily_disabled = False
if was_connected:
cls._connected = True # Simulate reconnection
@classmethod
def reset(cls):
"""Test helper to reset state."""
cls.wifi_busy = False
cls._connected = False
cls._temporarily_disabled = False
# Inject mocks
sys.modules['machine'] = MockMachine
sys.modules['mpos.net.wifi_service'] = type('module', (), {'WifiService': MockWifiService})()
# Now import BatteryManager
from mpos.battery_manager import BatteryManager
class TestADC2Detection(unittest.TestCase):
"""Test ADC1 vs ADC2 pin detection."""
def test_adc1_pins_detected(self):
"""Test that ADC1 pins (GPIO1-10) are detected correctly."""
from mpos.battery_manager import _is_adc2_pin
for pin in range(1, 11):
self.assertFalse(_is_adc2_pin(pin), f"GPIO{pin} should be ADC1")
def test_adc2_pins_detected(self):
"""Test that ADC2 pins (GPIO11-20) are detected correctly."""
from mpos.battery_manager import _is_adc2_pin
for pin in range(11, 21):
self.assertTrue(_is_adc2_pin(pin), f"GPIO{pin} should be ADC2")
def test_out_of_range_pins(self):
"""Test pins outside ADC range."""
from mpos.battery_manager import _is_adc2_pin
self.assertFalse(_is_adc2_pin(0))
self.assertFalse(_is_adc2_pin(21))
self.assertFalse(_is_adc2_pin(30))
self.assertFalse(_is_adc2_pin(100))
class TestInitADC(unittest.TestCase):
"""Test ADC initialization."""
def setUp(self):
"""Reset module state."""
import mpos.battery_manager as bm
bm._adc = None
bm._conversion_func = None
bm._adc_pin = None
def test_init_adc1_pin(self):
"""Test initializing with ADC1 pin."""
def adc_to_voltage(adc_value):
return adc_value * 0.00161
BatteryManager.init_adc(5, adc_to_voltage)
import mpos.battery_manager as bm
self.assertIsNotNone(bm._adc)
self.assertEqual(bm._conversion_func, adc_to_voltage)
self.assertEqual(bm._adc_pin, 5)
self.assertEqual(bm._adc._atten, MockADC.ATTN_11DB)
def test_init_adc2_pin(self):
"""Test initializing with ADC2 pin (should warn but work)."""
def adc_to_voltage(adc_value):
return adc_value * 0.00197
BatteryManager.init_adc(13, adc_to_voltage)
import mpos.battery_manager as bm
self.assertIsNotNone(bm._adc)
self.assertIsNotNone(bm._conversion_func)
self.assertEqual(bm._adc_pin, 13)
def test_conversion_func_stored(self):
"""Test that conversion function is stored correctly."""
def my_conversion(adc_value):
return adc_value * 0.12345
BatteryManager.init_adc(5, my_conversion)
import mpos.battery_manager as bm
self.assertEqual(bm._conversion_func, my_conversion)
class TestCaching(unittest.TestCase):
"""Test caching mechanism."""
def setUp(self):
"""Reset module state."""
BatteryManager.clear_cache()
def adc_to_voltage(adc_value):
return adc_value * 0.00161
BatteryManager.init_adc(5, adc_to_voltage) # Use ADC1 to avoid WiFi complexity
MockWifiService.reset()
def tearDown(self):
"""Clean up."""
BatteryManager.clear_cache()
def test_cache_hit_on_first_read(self):
"""Test that first read already has a cache (because of read during init) """
import mpos.battery_manager as bm
self.assertIsNotNone(bm._cached_raw_adc)
raw = BatteryManager.read_raw_adc()
self.assertIsNotNone(bm._cached_raw_adc)
self.assertEqual(raw, bm._cached_raw_adc)
def test_cache_hit_within_duration(self):
"""Test that subsequent reads use cache within duration."""
raw1 = BatteryManager.read_raw_adc()
# Change ADC value but should still get cached value
import mpos.battery_manager as bm
bm._adc.set_read_value(3000)
raw2 = BatteryManager.read_raw_adc()
self.assertEqual(raw1, raw2, "Should return cached value")
def test_force_refresh_bypasses_cache(self):
"""Test that force_refresh bypasses cache."""
import mpos.battery_manager as bm
bm._adc.set_read_value(2000)
raw1 = BatteryManager.read_raw_adc()
# Change value and force refresh
bm._adc.set_read_value(3000)
raw2 = BatteryManager.read_raw_adc(force_refresh=True)
self.assertNotEqual(raw1, raw2, "force_refresh should bypass cache")
self.assertEqual(raw2, 3000.0)
def test_clear_cache_works(self):
"""Test that clear_cache() clears the cache."""
BatteryManager.read_raw_adc()
import mpos.battery_manager as bm
self.assertIsNotNone(bm._cached_raw_adc)
BatteryManager.clear_cache()
self.assertIsNone(bm._cached_raw_adc)
self.assertEqual(bm._last_read_time, 0)
class TestADC1Reading(unittest.TestCase):
"""Test ADC reading with ADC1 (no WiFi interference)."""
def setUp(self):
"""Reset module state."""
BatteryManager.clear_cache()
def adc_to_voltage(adc_value):
return adc_value * 0.00161
BatteryManager.init_adc(5, adc_to_voltage) # GPIO5 is ADC1
MockWifiService.reset()
MockWifiService._connected = True
def tearDown(self):
"""Clean up."""
BatteryManager.clear_cache()
MockWifiService.reset()
def test_adc1_doesnt_disable_wifi(self):
"""Test that ADC1 reading doesn't disable WiFi."""
MockWifiService._connected = True
BatteryManager.read_raw_adc(force_refresh=True)
# WiFi should still be connected
self.assertTrue(MockWifiService.is_connected())
self.assertFalse(MockWifiService.wifi_busy)
def test_adc1_ignores_wifi_busy(self):
"""Test that ADC1 reading works even if WiFi is busy."""
MockWifiService.wifi_busy = True
# Should not raise error
try:
raw = BatteryManager.read_raw_adc(force_refresh=True)
self.assertIsNotNone(raw)
except RuntimeError:
self.fail("ADC1 should not raise error when WiFi is busy")
class TestADC2Reading(unittest.TestCase):
"""Test ADC reading with ADC2 (requires WiFi disable)."""
def setUp(self):
"""Reset module state."""
BatteryManager.clear_cache()
def adc_to_voltage(adc_value):
return adc_value * 0.00197
BatteryManager.init_adc(13, adc_to_voltage) # GPIO13 is ADC2
MockWifiService.reset()
def tearDown(self):
"""Clean up."""
BatteryManager.clear_cache()
MockWifiService.reset()
def test_adc2_disables_wifi_when_connected(self):
"""Test that ADC2 reading disables WiFi when connected."""
MockWifiService._connected = True
BatteryManager.read_raw_adc(force_refresh=True)
# WiFi should be reconnected after reading (if it was connected before)
self.assertTrue(MockWifiService.is_connected())
def test_adc2_sets_wifi_busy_flag(self):
"""Test that ADC2 reading sets wifi_busy flag."""
MockWifiService._connected = False
# wifi_busy should be False before
self.assertFalse(MockWifiService.wifi_busy)
BatteryManager.read_raw_adc(force_refresh=True)
# wifi_busy should be False after (cleared in finally)
self.assertFalse(MockWifiService.wifi_busy)
def test_adc2_raises_error_if_wifi_busy(self):
"""Test that ADC2 reading raises error if WiFi is busy."""
MockWifiService.wifi_busy = True
with self.assertRaises(RuntimeError) as ctx:
BatteryManager.read_raw_adc(force_refresh=True)
self.assertIn("WifiService is already busy", str(ctx.exception))
def test_adc2_uses_cache_when_wifi_busy(self):
"""Test that ADC2 uses cache even when WiFi is busy."""
# First read to populate cache
MockWifiService.wifi_busy = False
raw1 = BatteryManager.read_raw_adc(force_refresh=True)
# Now set WiFi busy
MockWifiService.wifi_busy = True
# Should return cached value without error
raw2 = BatteryManager.read_raw_adc()
self.assertEqual(raw1, raw2)
def test_adc2_only_reconnects_if_was_connected(self):
"""Test that ADC2 only reconnects WiFi if it was connected before."""
# WiFi is NOT connected
MockWifiService._connected = False
BatteryManager.read_raw_adc(force_refresh=True)
# WiFi should still be disconnected (no unwanted reconnection)
self.assertFalse(MockWifiService.is_connected())
class TestVoltageCalculations(unittest.TestCase):
"""Test voltage and percentage calculations."""
def setUp(self):
"""Reset module state."""
BatteryManager.clear_cache()
def adc_to_voltage(adc_value):
return adc_value * 0.00161
BatteryManager.init_adc(5, adc_to_voltage) # ADC1 pin, scale factor for 2:1 divider
import mpos.battery_manager as bm
bm._adc.set_read_value(2048) # Mid-range
def tearDown(self):
"""Clean up."""
BatteryManager.clear_cache()
def test_read_battery_voltage_applies_scale_factor(self):
"""Test that voltage is calculated correctly."""
import mpos.battery_manager as bm
bm._adc.set_read_value(2048) # Mid-range
BatteryManager.clear_cache()
voltage = BatteryManager.read_battery_voltage(force_refresh=True)
expected = 2048 * 0.00161
self.assertAlmostEqual(voltage, expected, places=4)
def test_voltage_clamped_to_zero(self):
"""Test that negative voltage is clamped to 0."""
import mpos.battery_manager as bm
bm._adc.set_read_value(0)
BatteryManager.clear_cache()
voltage = BatteryManager.read_battery_voltage(force_refresh=True)
self.assertGreaterEqual(voltage, 0.0)
def test_get_battery_percentage_calculation(self):
"""Test percentage calculation."""
# Set voltage to mid-range between MIN and MAX
import mpos.battery_manager as bm
mid_voltage = (bm.MIN_VOLTAGE + bm.MAX_VOLTAGE) / 2
# Inverse of conversion function: if voltage = adc * 0.00161, then adc = voltage / 0.00161
raw_adc = mid_voltage / 0.00161
bm._adc.set_read_value(int(raw_adc))
BatteryManager.clear_cache()
percentage = BatteryManager.get_battery_percentage()
self.assertAlmostEqual(percentage, 50.0, places=0)
def test_percentage_clamped_to_0_100(self):
"""Test that percentage is clamped to 0-100 range."""
import mpos.battery_manager as bm
# Test minimum
bm._adc.set_read_value(0)
BatteryManager.clear_cache()
percentage = BatteryManager.get_battery_percentage()
self.assertGreaterEqual(percentage, 0.0)
self.assertLessEqual(percentage, 100.0)
# Test maximum
bm._adc.set_read_value(4095)
BatteryManager.clear_cache()
percentage = BatteryManager.get_battery_percentage()
self.assertGreaterEqual(percentage, 0.0)
self.assertLessEqual(percentage, 100.0)
class TestAveragingLogic(unittest.TestCase):
"""Test that ADC readings are averaged."""
def setUp(self):
"""Reset module state."""
BatteryManager.clear_cache()
def adc_to_voltage(adc_value):
return adc_value * 0.00161
BatteryManager.init_adc(5, adc_to_voltage)
def tearDown(self):
"""Clean up."""
BatteryManager.clear_cache()
def test_adc_read_averages_10_samples(self):
"""Test that 10 samples are averaged."""
import mpos.battery_manager as bm
bm._adc.set_read_value(2000)
BatteryManager.clear_cache()
raw = BatteryManager.read_raw_adc(force_refresh=True)
# Should be average of 10 reads
self.assertEqual(raw, 2000.0)
class TestDesktopMode(unittest.TestCase):
"""Test behavior when ADC is not available (desktop mode)."""
def setUp(self):
"""Disable ADC."""
import mpos.battery_manager as bm
bm._adc = None
def adc_to_voltage(adc_value):
return adc_value * 0.00161
bm._conversion_func = adc_to_voltage
def test_read_raw_adc_returns_random_value(self):
"""Test that desktop mode returns random ADC value."""
raw = BatteryManager.read_raw_adc()
self.assertIsNotNone(raw)
self.assertTrue(raw > 0, f"Expected raw > 0, got {raw}")
self.assertTrue(raw < 4096, f"Expected raw < 4096, got {raw}")
def test_read_battery_voltage_works_without_adc(self):
"""Test that voltage reading works in desktop mode."""
voltage = BatteryManager.read_battery_voltage()
self.assertIsNotNone(voltage)
self.assertTrue(voltage > 0, f"Expected voltage > 0, got {voltage}")
def test_get_battery_percentage_works_without_adc(self):
"""Test that percentage reading works in desktop mode."""
percentage = BatteryManager.get_battery_percentage()
self.assertIsNotNone(percentage)
self.assertGreaterEqual(percentage, 0)
self.assertLessEqual(percentage, 100)
if __name__ == '__main__':
unittest.main()