diff --git a/software/src/app.py b/software/src/app.py index 01905cc..382deb3 100644 --- a/software/src/app.py +++ b/software/src/app.py @@ -6,7 +6,7 @@ import time from utils import TimerManager -Dependencies = namedtuple('Dependencies', ('mp3player', 'nfcreader', 'buttons', 'playlistdb', 'leds')) +Dependencies = namedtuple('Dependencies', ('mp3player', 'nfcreader', 'buttons', 'playlistdb', 'hwconfig', 'leds')) # Should be ~ 6dB steps VOLUME_CURVE = [1, 2, 4, 8, 16, 32, 63, 126, 251] @@ -44,6 +44,7 @@ class PlayerApp: self.player = deps.mp3player(self) self.nfc = deps.nfcreader(self.tag_state_machine) self.playlist_db = deps.playlistdb(self) + self.hwconfig = deps.hwconfig(self) self.leds = deps.leds(self) self.tag_mode = self.playlist_db.getSetting('tagmode') self.playing_tag = None @@ -97,6 +98,13 @@ class PlayerApp: self.mp3file = None self._play_next() + def onIdleTimeout(self): + if self.hwconfig.get_on_battery(): + self.hwconfig.power_off() + else: + # Check again in a minute + self.timer_manager.schedule(time.ticks_ms() + 60*1000, self.onIdleTimeout) + def _set_playlist(self, tag: bytes): if self.playlist is not None: pos = self.player.stop() @@ -136,7 +144,9 @@ class PlayerApp: self._onActive() def _onIdle(self): + self.timer_manager.schedule(time.ticks_ms() + 60*1000, self.onIdleTimeout) self.leds.set_state(self.leds.IDLE) def _onActive(self): + self.timer_manager.cancel(self.onIdleTimeout) self.leds.set_state(self.leds.PLAYING) diff --git a/software/src/hwconfig_Rev1.py b/software/src/hwconfig_Rev1.py index fed50bf..c9240a7 100644 --- a/software/src/hwconfig_Rev1.py +++ b/software/src/hwconfig_Rev1.py @@ -39,6 +39,7 @@ BUTTON_POWER = Pin.board.GP21 # Power POWER_EN = Pin.board.GP22 VBAT_ADC = Pin.board.GP26 +VBUS_DET = Pin.board.WL_GPIO2 def board_init(): @@ -61,3 +62,13 @@ def get_battery_voltage(): adc = machine.ADC(VBAT_ADC) # create ADC object on ADC pin battv = adc.read_u16()/65535.0*3.3*2 return battv + + +def power_off(): + POWER_EN.init(mode=Pin.OUT) + POWER_EN.value(0) + + +def get_on_battery(): + vbus = VBUS_DET.value() + return not vbus diff --git a/software/src/hwconfig_breadboard.py b/software/src/hwconfig_breadboard.py index c291b3e..9243682 100644 --- a/software/src/hwconfig_breadboard.py +++ b/software/src/hwconfig_breadboard.py @@ -47,3 +47,12 @@ def board_init(): def get_battery_voltage(): # Not supported on breadboard return None + + +def power_off(): + # Not supported on breadboard + pass + + +def get_on_battery(): + return False diff --git a/software/src/main.py b/software/src/main.py index 27658c0..673b182 100644 --- a/software/src/main.py +++ b/software/src/main.py @@ -78,6 +78,7 @@ def run(): pin_voldown=hwconfig.BUTTON_VOLDOWN, pin_next=hwconfig.BUTTON_NEXT), playlistdb=lambda _: playlistdb, + hwconfig=lambda _: hwconfig, leds=lambda _: LedManager(np)) the_app = app.PlayerApp(deps) diff --git a/software/tests/test_playerapp.py b/software/tests/test_playerapp.py index efa2dfb..fd839dc 100644 --- a/software/tests/test_playerapp.py +++ b/software/tests/test_playerapp.py @@ -101,6 +101,18 @@ class FakePlaylistDb: return None +class FakeHwconfig: + def __init__(self): + self.powered = True + self.on_battery = False + + def power_off(self): + self.powered = False + + def get_on_battery(self): + return self.on_battery + + class FakeLeds: IDLE = 0 PLAYING = 1 @@ -124,11 +136,12 @@ def faketimermanager(monkeypatch): def _makedeps(mp3player=FakeMp3Player, nfcreader=FakeNfcReader, buttons=FakeButtons, - playlistdb=FakePlaylistDb, leds=FakeLeds): + playlistdb=FakePlaylistDb, hwconfig=FakeHwconfig, leds=FakeLeds): return app.Dependencies(mp3player=lambda _: mp3player() if callable(mp3player) else mp3player, nfcreader=lambda x: nfcreader(x) if callable(nfcreader) else nfcreader, buttons=lambda _: buttons() if callable(buttons) else buttons, playlistdb=lambda _: playlistdb() if callable(playlistdb) else playlistdb, + hwconfig=lambda _: hwconfig() if callable(hwconfig) else hwconfig, leds=lambda _: leds() if callable(leds) else leds) @@ -278,3 +291,32 @@ def test_led_state(micropythonify, faketimermanager, monkeypatch): FakeNfcReader.tag_callback.onTagChange(None) faketimermanager.testing_run_queued() assert fake_leds.state == FakeLeds.IDLE + + +def test_idle_shutdown_after_start(micropythonify, faketimermanager, monkeypatch): + fake_hwconfig = FakeHwconfig() + fake_hwconfig.on_battery = True + deps = _makedeps(hwconfig=fake_hwconfig) + app.PlayerApp(deps) + assert fake_hwconfig.powered + faketimermanager.testing_run_queued() + assert not fake_hwconfig.powered + + +def test_idle_shutdown_after_playback(micropythonify, faketimermanager, monkeypatch): + fake_hwconfig = FakeHwconfig() + fake_hwconfig.on_battery = True + deps = _makedeps(hwconfig=fake_hwconfig) + app.PlayerApp(deps) + assert fake_hwconfig.powered + with monkeypatch.context() as m: + m.setattr(builtins, 'open', fake_open) + FakeNfcReader.tag_callback.onTagChange([23, 42, 1, 2, 3]) + faketimermanager.testing_run_queued() + assert fake_hwconfig.powered + # Stop playback + FakeNfcReader.tag_callback.onTagChange(None) + faketimermanager.testing_run_queued() + # Elapse idle timer + faketimermanager.testing_run_queued() + assert not fake_hwconfig.powered