diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index ceccf00..822fb5d --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ Configuration is done through a JSON file named `config.json` in the same direct "code": "", "comboA": 100, "comboB": 300, - "comboABreak": ["vibrate", 20, 1.0], - "comboBBreak": ["shock": 5, 1.0], - "fail": ["shock", 10, 2.0] + "comboABreak": ["vibrate", 1.0, 20], + "comboBBreak": ["shock", 1.0, 5], + "fail": ["shock", 2.0, 10] } ``` The values for combo breaks and fail is an array. -1. `vibrate`, `shock`, and `beep`. -2. Intensity -3. Duration +1. `vibrate`, `shock`, and `beep` (if action is `beep`, intensity is ignored) +2. Duration +3. Intensity diff --git a/datapuller.py b/datapuller.py old mode 100644 new mode 100755 index e80476e..99e8d7b --- a/datapuller.py +++ b/datapuller.py @@ -21,14 +21,24 @@ class BaseData: async with websockets.connect( 'ws://{}:{}{}'.format(self.host, self.port, self.path)) as websocket: async for message in websocket: - await self._handle(message) + logger.debug(message) + if message: + self._handle(json.loads(message)) except Exception as e: logger.critical(e) + raise e class LiveData(BaseData): TRIGGERS = enum.Enum('Triggers', ['Unknown', 'TimerElapsed', 'NoteMissed', 'EnergyChange', 'ScoreChange']) + TRIGGERS_INVERSE = [ + TRIGGERS.Unknown, + TRIGGERS.TimerElapsed, + TRIGGERS.NoteMissed, + TRIGGERS.EnergyChange, + TRIGGERS.ScoreChange, + ] def __init__(self, host: str = '127.0.0.1', port: int = 2946, path: str = '/BSDataPuller/LiveData'): @@ -41,8 +51,10 @@ class LiveData(BaseData): self.events[event].append(function) def _handle(self, message: str): - for event in events[TRIGGERS(message['EventTrigger']).name]: - event(message) + invert = self.TRIGGERS_INVERSE[message['EventTrigger']].name + if invert in self.events: + for event in self.events[invert]: + event(message) class MapData(BaseData): diff --git a/poetry.lock b/poetry.lock old mode 100644 new mode 100755 index 4511e41..f504baf --- a/poetry.lock +++ b/poetry.lock @@ -66,8 +66,8 @@ python-versions = ">=3.7" [metadata] lock-version = "1.1" -python-versions = "^3.10" -content-hash = "5a734eed883538e1b3f3ec6d75d33bfcf056ecd3a28311b0e975c847bb268ce2" +python-versions = "^3.9" +content-hash = "a1bceaf7911989ee719f409e1eb231de0d8e0d8ea8faebf994892b0f12f651cd" [metadata.files] certifi = [ diff --git a/pyproject.toml b/pyproject.toml old mode 100644 new mode 100755 index 4b13725..83b7f4e --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ license = "MIT" readme = "README.md" [tool.poetry.dependencies] -python = "^3.10" +python = "^3.9" requests = "^2.28.1" [tool.poetry.group.dev.dependencies] diff --git a/pyshock.py b/pyshock.py old mode 100644 new mode 100755 index 02ddc72..505d751 --- a/pyshock.py +++ b/pyshock.py @@ -1,7 +1,6 @@ from enum import Enum import json import logging -from threading import Timer import requests @@ -15,16 +14,13 @@ class PiShock: ACTIONS = Enum('ACTIONS', ['shock', 'vibrate', 'beep']) def __init__(self, username: str, api_key: str, code: str, name: str = '', - max_intensity: int = 50, max_duration: int = 5, throttle: int = 5): + max_intensity: int = 50, max_duration: int = 5): self.username = username self.api_key = api_key self.code = code self.name = name if name else self.NAME self.max_intensity = max_intensity self.max_duration = max_duration - self.throttle = throttle - self.throttled = False - self.throttle_timer = None def shock(self, duration: int = 1, intensity: int = 5): self._act(self.ACTIONS.shock, duration, intensity) @@ -42,9 +38,6 @@ class PiShock: logger.warning('Action not found: {}', e) def _act(self, op: str, duration: int = 1, intensity: int = 5): - if op == self.ACTIONS.shock and self.throttled: - logger.error('Request throttled') - return if duration > self.max_duration: logger.error('%d is greater than maximum allowed duration, %d', duration, self.max_duration) @@ -71,7 +64,3 @@ class PiShock: logger.debug(res.text) if res.status_code != 200 or res.text != 'Operation Succeeded.': logger.warning('Error communicating with shocker: %s', res.text) - - if op == self.ACTIONS.shock: - self.throttled = True - self.throttle_timer = Timer(self.throttle, lambda: self.throttled == False) diff --git a/zapsaber.py b/zapsaber.py old mode 100644 new mode 100755 index 247ce98..116da56 --- a/zapsaber.py +++ b/zapsaber.py @@ -10,72 +10,75 @@ from datapuller import ( logging.basicConfig(level=logging.INFO) - - -playing = False -combo_level = None -shocker = None -failed = False -events = None +logger = logging.getLogger(__name__) class Events: combo_A = 100 combo_B = 300 - combo_A_break = ['vibrate', 20, 0.5] - combo_B_break = ['shock', 5, 0.3] + combo_A_break = ['vibrate', 0.5, 20] + combo_B_break = ['shock', 0.3, 5] - fail = ['shock', 10, 1] + fail = ['shock', 1, 10] - def __init__(self, config): + playing = False + combo_level = None + map_failed = False + + def __init__(self, config, shocker): self.combo_A = config['comboA'] self.combo_B = config['comboB'] self.combo_A_break = config['comboABreak'] self.combo_B_break = config['comboBBreak'] self.fail = config['fail'] + self.shocker = shocker + def update_combo(self, message): + combo = message['Combo'] + health = message['PlayerHealth'] + if not self.map_failed and health == 0: + logger.info('Failed the map!') + self.shocker.act(*self.fail) + self.map_failed = True + self.combo_level = None + if combo > self.combo_A: + self.combo_level = 'A' + if combo > self.combo_B: + self.combo_level = 'B' + def break_combo(self, message): + if self.combo_level == 'A': + logger.info('Broke combo A!') + self.shocker.act(*self.combo_A_break) + if self.combo_level == 'B': + logger.info('Broke combo B!') + self.shocker.act(*self.combo_B_break) + self.combo_level = None -def update_combo(message): - combo = message['Combo'] - if combo > events.combo_A: - combo_level = 'A' - if combo > events.combo_B: - combo_level = 'B' - - -def break_combo(message): - if combo_level == 'A': - logger.info('Broke combo A!') - shocker.act(*events.combo_A_break) - if combo_level == 'B': - logger.info('Broke combo B!') - shocker.act(*events.combo_B_break) - combo_level = None - - -def map_update(message): - if map_failed and not message['LevelFailed']: - map_failed = false - elif not map_failed and message['LevelFailed']: - logger.info('Failed the map!') - shocker.act(*events.fail) - map_failed = true - combo_level = None + def tick(self, message): + logger.info('Tick') + if self.playing and message['TimeElapsed'] == 0: + logger.info('Stopping map...') + self.map_failed = False + self.combo = 0 + self.playing = False + elif not self.playing and message['TimeElapsed'] != 0: + logger.info('Starting map...') + self.playing = True -async def main(username, api_key, code): - logging.info('Initializing...') - shocker = PiShock(username, api_key, code, name='ZapSaber') - shocker.beep() +async def main(config): + logger.info('Initializing...') + shocker = PiShock(config['username'], config['apiKey'], config['code'], name='ZapSaber') + #shocker.beep() + events = Events(config, shocker) livedata = LiveData() - mapdata = MapData() - livedata.on('ScoreChange', update_combo) - livedata.on('NoteMissed', break_combo) - mapdata.on_update(map_update) + livedata.on('ScoreChange', events.update_combo) + livedata.on('NoteMissed', events.break_combo) + livedata.on('TimerElapsed', events.tick) await livedata.listen() await mapdata.listen() @@ -85,5 +88,4 @@ async def main(username, api_key, code): if __name__ == '__main__': with open('config.json') as config_file: config = json.load(config_file) - events = Events(config) - asyncio.run(main(config['username'], config['apiKey'], config['code'])) + asyncio.run(main(config))