aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--abcontroller.py3
-rw-r--r--abcontroller_unittest.py4
-rw-r--r--diagram.drawio2
-rw-r--r--known-issues.md1
-rw-r--r--midi_controller_launchpad_mini.py21
-rw-r--r--midi_launchpad_mini_integrationtest.py57
-rw-r--r--notifier.py1
-rw-r--r--notifier_unittest.py1
-rw-r--r--solo_tool.py10
-rw-r--r--solo_tool_cli.py10
-rw-r--r--solo_tool_cli_integrationtest.py4
-rw-r--r--solo_tool_integrationtest.py38
12 files changed, 124 insertions, 28 deletions
diff --git a/abcontroller.py b/abcontroller.py
index 99bd9d7..cec9fb2 100644
--- a/abcontroller.py
+++ b/abcontroller.py
@@ -66,6 +66,9 @@ class ABController:
def setEnable(self, enable):
self._enabled = enable
+ def isEnabled(self):
+ return self._enabled
+
def getStoredLimits(self, song):
return self._limits.get(song)
diff --git a/abcontroller_unittest.py b/abcontroller_unittest.py
index f9e947d..9fdcbc3 100644
--- a/abcontroller_unittest.py
+++ b/abcontroller_unittest.py
@@ -103,11 +103,15 @@ def test_disableAbRepeat():
uut.loadLimits(0)
assert uut.getLoadedIndex() == 0
+ assert uut.isEnabled()
+
uut.setEnable(False)
checkLimits(uut, abLimits.a, abLimits.b, fail=True)
+ assert not uut.isEnabled()
uut.setEnable(True)
checkLimits(uut, abLimits.a, abLimits.b)
+ assert uut.isEnabled()
def test_storeLimitsToSpecificSong():
song = "/path/to/song"
diff --git a/diagram.drawio b/diagram.drawio
index 935a914..e3d1846 100644
--- a/diagram.drawio
+++ b/diagram.drawio
@@ -1 +1 @@
-<mxfile host="app.diagrams.net" modified="2022-01-04T12:39:41.427Z" agent="5.0 (X11)" etag="pkS41rbGQOjqmha5821z" version="16.1.4" type="device" pages="3"><diagram id="g-wcGVps3MkI6_XAwNEs" name="Core">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</diagram><diagram id="yK3rgzEW7m2RTtpwjvJ6" name="MIDI">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</diagram><diagram id="PyNSc7ezSt42GBdjTBvd" name="Launchpad">5ZzNkto6EIWfhmWq3JJ/pGUyd5Iskk1mkbp358ECnGsQZczA5OljwGaMWlQ5VUC7mVXslrDH5yjW121bI/kw334p0+Xsu81MMRJBth3Jf0ZCQCiC+p9d5PUQUVocAtMyz5pOb4Gn/Ldpgs3vpus8M6uTjpW1RZUvT4Nju1iYcXUSS8vSbk67TWxxetZlOjUo8DROCxz9mWfVrLkKkbzFv5p8OmvPDLE+tMzTtnNzJatZmtlNJyQfR/KhtLY6bM23D6bYidfq8l/1a2Ie5Xoz+bH6PFv8nHz799uHw8E+/81PjpdQmkV12UPL5mJf0mJtjpcWF/VpPj2X9dZ0t/Vii/Xc4HhU940acarXVvHSrheZ2Z0V6j6bWV6Zp2U63rVu6jFWx2bVvGiaa9urNF+Yst4P6v1JXhQPtrDl/lgyi4zKwjq+qkr7v+m0KPEs47hu6SlOI+KLKSuz7QyNRqwvxs5NVb7WXZrWsLG9GfdJs7vpDKImNOuMnzaWNsN2ejzumzX1RuPOXzjV/jkdpyDGlqyWppaewKnJxMTjsc+pLNHPQXA7pyBKiK0CZJUUlxX/RlKKMCKWUiApQ8VSStkOATIpJZIyDllKGYImljJEUrbnYiZlFMTEUkZISu2b1iq7vO70pcbGP309qyiMbmmJDoktiTFpgMCeLIv0dXcm3JCuV+adwEasgNYthe9F0J/g43smeJDDQniF73WQ9Eb461tF+L/KtYqc4RW+CUrJY4p3tSSHeJUgLUPNU0tyilcKaRlf+K5wKy3JMV7h4lc7m3PTkpzjNS5P6YSpltQArnH9CIDp5EPOxxqXPTy5zDk+Tu6Zj49PNgbCx9qTyqjefHx9qwj52LWKnI81zmUkk2qiqyU5H2uca0RMyomuluR8rHGuEcc8tSTnY41zDSV4aknPxzjX0EweZCEtqfkYAs+zcGA6+5ADMgQCT+X9CVndMyFLGBYhQ4Czmd0s0RORr+8VISK7XpEjMgQ4n5FManWumOSMDAFOOCImxTpXTHJIhgBnHDGTap0rJjklQ4BTDsWkWueKSY7JEOCco0VndmLSczJOOgCYzkD0nAy4LB/252R9z5wcBgPjZMA5je/F9jOcfH2vSF9gGhonA05qJJOanSsmPScDTjoiwVNMek4GnHTETKp2rpj0nAw46VBMinaumPScDDjpOJZF2alJDsqAs47jcOWmJj0otxXu7hTUH5T3w/h+SVkPjJQFzmp8L5CfIeUbmEWJynpoqCxwXiOZFO5cMelRWeC8I2JSuHPFpEdlgfOOmEnhzhWTHpUFzjvUhe+ytxKTHpWF53u2wDPF/VrPlzha2fqnH9/LDEeP4sKT2ADTKW4AKI6fAHi+rj2L4nB5uruW9pEaGFi3L8J3wVr0B2vG0tNjsvSswMGkbueKSY/JLVp2MZlJ3c4Vkx6TJc452jsVNzHpMVl61uFgUrVzxaTHZIlzjv2roWh1h9K85Ha9wi0fP11W++Es0YHcIsdk6Vukw/Ox1Hm3VnYxfS9+0YO4xE8YPEs/nAVxwYcG42RoII5zIN8L7udAnLH09CDuWbWwtYObmPQg7llXMGI6MulB3LOyYMLkfXJXTHoQ96wtqJjU7Fwx6UHcs7rg/rsfd2ZamG11cwgnfHSOnCKHcM/iheD7HMvv1NUBfEBeXRHA6923Nbj3bZ2VzOXjHw==</diagram></mxfile> \ No newline at end of file
+<mxfile host="app.diagrams.net" modified="2022-02-03T17:57:29.948Z" agent="5.0 (X11)" etag="JSVR_wePj1eIi_OTfR3Q" version="16.5.3" type="device" pages="3"><diagram id="g-wcGVps3MkI6_XAwNEs" name="Core">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</diagram><diagram id="yK3rgzEW7m2RTtpwjvJ6" name="MIDI">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</diagram><diagram id="PyNSc7ezSt42GBdjTBvd" name="Launchpad">5ZxNc6M4EIZ/jY9bRUt8SMdMNjNzmLlsDlO7N2Jkm1lsuTD+yP76xTY4RC1XkSrbTTunQEuB8LZCP91Aj+TjfPetTJeznzYzxUgE2W4k/xwJAaEI6h97y+vRorQ4GqZlnjWT3gzP+X+mMTa/N13nmVm9m1hZW1T58r1xbBcLM67e2dKytNv30ya2eH/WZTo1yPA8Tgts/ZVn1ay5CpG82b+bfDprzwyxPo7M03ZycyWrWZrZbcckn0bysbS2Om7Nd4+m2IvX6vJP9XtinuR6O/lr9XW2+DX58fePP44H+/qRXzldQmkW1WUPLZuL3aTF2pwuLS7q03x5Keut6X5rY4v13GB7VM+NGnGq11bx0q4XmdmfFeo521lemedlOt6Pbus1Vttm1bxohmu3V2m+MGW9H9T7k7woHm1hy8OxZBYZlYW1fVWV9l/TGVHiRcZxPdJTnEbEjSkrs+ssjUasb8bOTVW+1lOa0bBxe7Puk2Z321lEjWnWWT+tLW2W7fR03DfX1BuNdz7gqfbP6XgKYuyS1dLU0hN4ajIx8Xjs81SW6JcguJ2nIEqIXQXIVVJcVvwbSSnCiFhKgaQMFUspZbsEyKSUSMo4ZCllCJpYyhBJ2Z6LmZRREBNLGSEptS+sVXZ53fClxsYfvl5UFEa3dIkOiV0SY9IAgX2yLNLX/ZnwQLpemU8CG7ECWm8pfC+C/gQf3zPBgxwWwit8r4OkN8Jf31WE/1Wuq8gZXuGboJQ8QryrJTnEqwRpGWqeWpJTvFJIy/jCd4VbaUmO8QoXv9pozk1Lco7XuDylE6ZaUgO4xvUjAKbBh5yPNS57eHKZc3yc3DMfn55sDISPtSeVUb35+PquIuRj11XkfKxxLiOZVBNdLcn5WONcI2JSTnS1JOdjjXONOOapJTkfa5xrKMFTS3o+xrmGZvIgC2lJzccQeJ6FA9PoQw7IEAgcyvsTsrpnQpYwLEKGAGcz+yjRE5Gv7ytCRHZ9RY7IEOB8RjKp1blikjMyBDjhiJgU61wxySEZApxxxEyqda6Y5JQMAU45FJNqnSsmOSZDgHOONo5l+aYNZPoU8+qzdOyeqb/X82XvyZWtT/2AA+e5A3yOSDoA4MfZEwDTUEoP/ICfL4T9gV/fM/CHwcCAH3By5ntD/wzwX99XpG9iDQ34AWdnkknx0RWTHvgBZ0+R4CkmPfADzp5iJuVHV0x64AecPSkm1UdXTHrgB5w9neq77NQkB2XA6dNpuXJTkx6U21J9NwT1B+XDMr5fUtYDI2WBsxrfm/BnSPkGzqJEZT00VBY4r5FMKpCumPSoLHDeETGpQLpi0qOywHlH2xKAm5j0qCxw3qEufJe9lZj0qCxw3uGpYUMAH6h3T6eF6T394csgSuMDoh564hee/AmYRtIBED9+0OD5Gvks8cPlIfJa2kdqYPzefjjQ5XfRn98ZS09P49LTsYRJedAVk57GW4Lt0jiT8qArJj2NS5zatHcqbmLS07j09C1hUhx0xaSncYlTm8OrtKgbRmk2uV2v8MjDld8goWtpgrxFjsnS19TE83HZeW+t7GL6WfxFD+ISP8jwtMo4C+KCDw3GydBAHOdAvg8CzoE4Y+npQdzT5bF1Bzcx6UHc04cxYroy6UHc04kxYfL+vSsmPYh7ejEqJjU7V0x6EPd0Yzx8J+VGpoXZVTeHcMJaNfIUOYR7mj2C7/M1v6euDuAD8tUVAbzefetZfhjrdH6XT/8D</diagram></mxfile> \ No newline at end of file
diff --git a/known-issues.md b/known-issues.md
index b1bac9e..ae248f5 100644
--- a/known-issues.md
+++ b/known-issues.md
@@ -27,6 +27,7 @@
* Key mapping in Qt to jump to A
* Space bar in principle
* Not so easy to do actually, used Super L instead
+* AB repeat toggle in MIDI controller
# Use Cases
diff --git a/midi_controller_launchpad_mini.py b/midi_controller_launchpad_mini.py
index 6790cec..0cc952c 100644
--- a/midi_controller_launchpad_mini.py
+++ b/midi_controller_launchpad_mini.py
@@ -28,12 +28,14 @@ class MidiController:
self._soloTool.registerPlayingStateCallback(self._updatePlayPauseButton)
self._soloTool.registerPlaybackVolumeCallback(self._updateVolumeRow)
self._soloTool.registerPlaybackRateCallback(self._updateRateRow)
+ self._soloTool.registerAbLimitEnabledCallback(self._updateToggleAbLimitEnableButton)
def _registerHandlers(self):
self._handlers = {
- 96 : self._stop,
+ 96 : self._soloTool.stop,
+ 99 : self._soloTool.jumpToA,
112 : self._playPause,
- 101 : self._soloTool.jumpToA,
+ 101 : self._toggleAbLimitEnable,
102 : self._soloTool.previousStoredAbLimits,
103 : self._soloTool.nextStoredAbLimits,
118 : self._soloTool.previousSong,
@@ -66,8 +68,8 @@ class MidiController:
else:
self._soloTool.play()
- def _stop(self):
- self._soloTool.stop()
+ def _toggleAbLimitEnable(self):
+ self._soloTool.setAbLimitEnable(not self._soloTool.isAbLimitEnabled())
def _updatePlayPauseButton(self, playing):
if playing:
@@ -75,6 +77,12 @@ class MidiController:
else:
self._setButtonLED(7, 0, MidiController.LED_YELLOW)
+ def _updateToggleAbLimitEnableButton(self, enabled):
+ if enabled:
+ self._setButtonLED(6, 5, MidiController.LED_GREEN)
+ else:
+ self._setButtonLED(6, 5, MidiController.LED_RED)
+
def _updateVolumeRow(self, volume):
t1 = int(round(volume / MidiController.PLAYBACK_VOLUME_STEP, 1))
t2 = int(round(MidiController.MIN_PLAYBACK_VOLUME / MidiController.PLAYBACK_VOLUME_STEP, 1))
@@ -123,8 +131,11 @@ class MidiController:
self._setButtonLED(6, 0, MidiController.LED_RED)
self._updatePlayPauseButton(self._soloTool.isPlaying())
+ # AB repeat toggle
+ self._updateToggleAbLimitEnableButton(self._soloTool.isAbLimitEnabled())
+
# AB control
- self._setButtonLED(6, 5, MidiController.LED_YELLOW)
+ self._setButtonLED(6, 3, MidiController.LED_YELLOW)
self._setButtonLED(6, 6, MidiController.LED_RED)
self._setButtonLED(6, 7, MidiController.LED_GREEN)
diff --git a/midi_launchpad_mini_integrationtest.py b/midi_launchpad_mini_integrationtest.py
index 615ef49..e9bc6b5 100644
--- a/midi_launchpad_mini_integrationtest.py
+++ b/midi_launchpad_mini_integrationtest.py
@@ -9,6 +9,15 @@ LED_YELLOW = 126
LED_GREEN = 124
LED_OFF = 0
+nextSongButton = 119
+previousSongButton = 118
+playPauseButton = 112
+stopButton = 96
+nextLimitButton = 103
+previousLimitButton = 102
+abToggleButton = 101
+jumpToAButton = 99
+
class MidiWrapperMock:
def __init__(self):
self.callback = None
@@ -56,8 +65,6 @@ def test_connect(uut, midiWrapperMock):
assert midiWrapperMock.connectedDevice == expectedDevice
def test_startStopAndPauseButtons(uut, midiWrapperMock, playerMock):
- playPauseButton = 112
- stopButton = 96
uut.connect()
assert playerMock.state == PlayerMock.STOPPED
@@ -88,8 +95,6 @@ def test_startStopAndPauseButtons(uut, midiWrapperMock, playerMock):
assert playerMock.state == PlayerMock.STOPPED
def test_startPauseButtonLed(uut, midiWrapperMock, playerMock, soloTool):
- playPauseButton = 112
- stopButton = 96
uut.connect()
assert playerMock.state == PlayerMock.STOPPED
@@ -110,6 +115,26 @@ def test_startPauseButtonLed(uut, midiWrapperMock, playerMock, soloTool):
playerMock.simulatePlayingStateChanged()
assert midiWrapperMock.getLatestMessage() == (playPauseButton, MidiController.LED_GREEN, 0)
+def test_abToggleButton(uut, midiWrapperMock, soloTool):
+ uut.connect()
+
+ midiWrapperMock.simulateInput(abToggleButton)
+ assert soloTool.isAbLimitEnabled()
+ assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_GREEN, 0)
+
+ midiWrapperMock.simulateInput(abToggleButton)
+ assert not soloTool.isAbLimitEnabled()
+ assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_RED, 0)
+
+def test_abToggleButtonLed(uut, midiWrapperMock, soloTool):
+ uut.connect()
+
+ soloTool.setAbLimitEnable(True)
+ assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_GREEN, 0)
+
+ soloTool.setAbLimitEnable(False)
+ assert midiWrapperMock.getLatestMessage() == (abToggleButton, MidiController.LED_RED, 0)
+
def test_jumpToAButton(uut, midiWrapperMock, soloTool, playerMock):
ab = (0.5, 0.6)
uut.connect()
@@ -117,12 +142,10 @@ def test_jumpToAButton(uut, midiWrapperMock, soloTool, playerMock):
soloTool.setAbLimits(ab[0], ab[1])
assert playerMock.position == 0.0
- midiWrapperMock.simulateInput(101)
+ midiWrapperMock.simulateInput(jumpToAButton)
assert playerMock.position == ab[0]
def test_previousAndNextSongButtons(uut, midiWrapperMock, soloTool, playerMock):
- nextSongButton = 119
- previousSongButton = 118
songs = [
"test.flac",
"test.mp3"
@@ -145,8 +168,6 @@ def test_previousAndNextSongButtons(uut, midiWrapperMock, soloTool, playerMock):
assert playerMock.currentSong == songs[0]
def test_previousAndNextAbButtons(uut, midiWrapperMock, soloTool, playerMock):
- nextLimitButton = 103
- previousLimitButton = 102
song = "test.flac"
abLimits = [
[0.2, 0.4],
@@ -330,13 +351,14 @@ def test_initializationMessages(uut, midiWrapperMock):
[(i, LED_GREEN, 0) for i in range(0, 6)] + # volume row
[(i, LED_YELLOW, 0) for i in range(16, 22)] + # playback rate row
[
- (96, LED_RED, 0),
- (101, LED_YELLOW, 0),
- (102, LED_RED, 0),
- (103, LED_GREEN, 0),
- (112, LED_YELLOW, 0),
- (118, LED_RED, 0),
- (119, LED_GREEN, 0)
+ (stopButton, LED_RED, 0),
+ (playPauseButton, LED_YELLOW, 0),
+ (abToggleButton, LED_RED, 0),
+ (jumpToAButton, LED_YELLOW, 0),
+ (previousLimitButton, LED_RED, 0),
+ (nextLimitButton, LED_GREEN, 0),
+ (previousSongButton, LED_RED, 0),
+ (nextSongButton, LED_GREEN, 0)
]
)
@@ -346,9 +368,6 @@ def test_initializationMessages(uut, midiWrapperMock):
assert sentMessagesSet == expectedMessages
def test_playingFeedbackWhenChangingSong(uut, midiWrapperMock, soloTool, playerMock):
- nextSongButton = 119
- previousSongButton = 118
- playPauseButton = 112
songs = [
"test.flac",
"test.mp3"
diff --git a/notifier.py b/notifier.py
index 06b4434..fae41b9 100644
--- a/notifier.py
+++ b/notifier.py
@@ -5,6 +5,7 @@ class Notifier:
PLAYBACK_RATE_EVENT = 2
CURRENT_SONG_EVENT = 3
CURRENT_AB_EVENT = 4
+ AB_LIMIT_ENABLED_EVENT = 5
def __init__(self, player):
self._callbacks = dict()
diff --git a/notifier_unittest.py b/notifier_unittest.py
index 4aff8f4..b27a17f 100644
--- a/notifier_unittest.py
+++ b/notifier_unittest.py
@@ -38,6 +38,7 @@ def test_allEvents(uut):
checkEvent(uut, Notifier.PLAYBACK_RATE_EVENT)
checkEvent(uut, Notifier.CURRENT_SONG_EVENT)
checkEvent(uut, Notifier.CURRENT_AB_EVENT)
+ checkEvent(uut, Notifier.AB_LIMIT_ENABLED_EVENT)
def test_eventWithoutRegisteredCallbacks(uut):
uut.notify(Notifier.PLAYING_STATE_EVENT, 0)
diff --git a/solo_tool.py b/solo_tool.py
index 4ea081d..6cb1a77 100644
--- a/solo_tool.py
+++ b/solo_tool.py
@@ -74,7 +74,14 @@ class SoloTool:
return list()
def setAbLimitEnable(self, enable):
+ previous = self._abController.isEnabled()
self._abController.setEnable(enable)
+ new = self._abController.isEnabled()
+ if previous != new:
+ self._notifier.notify(Notifier.AB_LIMIT_ENABLED_EVENT, new)
+
+ def isAbLimitEnabled(self):
+ return self._abController.isEnabled()
def nextStoredAbLimits(self):
previous = self._abController.getLoadedIndex()
@@ -152,3 +159,6 @@ class SoloTool:
def registerCurrentAbLimitsCallback(self, callback):
self._notifier.registerCallback(Notifier.CURRENT_AB_EVENT, callback)
+ def registerAbLimitEnabledCallback(self, callback):
+ self._notifier.registerCallback(Notifier.AB_LIMIT_ENABLED_EVENT, callback)
+
diff --git a/solo_tool_cli.py b/solo_tool_cli.py
index 8d53a33..43e3a9d 100644
--- a/solo_tool_cli.py
+++ b/solo_tool_cli.py
@@ -1,10 +1,12 @@
import sys
+import time
+import threading
from solo_tool import SoloTool
from midi_controller_launchpad_mini import MidiController
class SoloToolCLI:
- def __init__(self, sessionJson, soloToolOverride=None, midiOverride=None):
+ def __init__(self, sessionJson, soloToolOverride=None, midiOverride=None, tickEnable=True):
self._soloTool = SoloTool() if soloToolOverride is None else soloToolOverride
self._soloTool.loadSession(sessionJson)
self._midiController = MidiController(self._soloTool) if midiOverride is None else midiOverride
@@ -12,6 +14,8 @@ class SoloToolCLI:
"song" : self._song,
"midi" : self._midi
}
+ if tickEnable:
+ self._tick()
def input(self, commandString):
split = commandString.strip().split(" ")
@@ -34,6 +38,10 @@ class SoloToolCLI:
else:
print("Supported device: Novation Launchpad Mini MkII")
+ def _tick(self):
+ self._soloTool.tick()
+ threading.Timer(0.1, self._tick).start()
+
def main(args):
if len(args) == 0:
print("Please provide path to session file")
diff --git a/solo_tool_cli_integrationtest.py b/solo_tool_cli_integrationtest.py
index 9134dcf..25ef131 100644
--- a/solo_tool_cli_integrationtest.py
+++ b/solo_tool_cli_integrationtest.py
@@ -27,7 +27,7 @@ def mockMidi(soloTool):
@pytest.fixture
def uut(soloTool, mockMidi):
- return SoloToolCLI("test_session.json", soloToolOverride=soloTool, midiOverride=mockMidi)
+ return SoloToolCLI("test_session.json", soloToolOverride=soloTool, midiOverride=mockMidi, tickEnable=False)
def test_songSelection(uut, soloTool, mockPlayer):
expectedOutput = """\
@@ -56,7 +56,6 @@ Songs:
uut.input("song")
-
assert buf.getvalue() == expectedOutput
def test_connectMidi(uut, mockMidi):
@@ -73,3 +72,4 @@ Connecting to MIDI device...
assert mockMidi.connected
assert buf.getvalue() == expectedOutput
+
diff --git a/solo_tool_integrationtest.py b/solo_tool_integrationtest.py
index ee99303..5731eac 100644
--- a/solo_tool_integrationtest.py
+++ b/solo_tool_integrationtest.py
@@ -137,6 +137,15 @@ def test_addAndSetAbLimits(uut, mockPlayer):
uut.tick()
assert mockPlayer.position == 0.1
+def test_abLimitEnabledGetter(uut):
+ assert not uut.isAbLimitEnabled()
+
+ uut.setAbLimitEnable(True)
+ assert uut.isAbLimitEnabled()
+
+ uut.setAbLimitEnable(False)
+ assert not uut.isAbLimitEnabled()
+
def test_multipleSongsAndAbLimits(uut, mockPlayer):
songs = [
"test.flac",
@@ -554,3 +563,32 @@ def test_currentAbNotification(uut):
uut.nextStoredAbLimits()
assert not called
+
+def test_abLimitEnabledNotification(uut):
+ called = False
+ receivedValue = None
+ def callback(value):
+ nonlocal called, receivedValue
+ called = True
+ receivedValue = value
+
+ uut.registerAbLimitEnabledCallback(callback)
+ assert not called
+
+ uut.setAbLimitEnable(False)
+ assert not called
+ assert receivedValue is None
+
+ uut.setAbLimitEnable(True)
+ assert called
+ assert receivedValue == True
+ called = False
+ receivedValue = None
+
+ uut.setAbLimitEnable(True)
+ assert not called
+ assert receivedValue is None
+
+ uut.setAbLimitEnable(False)
+ assert called
+ assert receivedValue == False