diff --git a/.gitignore b/.gitignore index 7b9a2dc..78eeca7 100644 --- a/.gitignore +++ b/.gitignore @@ -155,4 +155,7 @@ cython_debug/ .vscode # Testcase for personal use -test/ \ No newline at end of file +test/ + +# history +.history/ \ No newline at end of file diff --git a/README.md b/README.md index a3178d6..2e17254 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ ## 快速上手 +**注意:郊狼2.0与3.0的使用方式并不相同!!!** + ### 安装 ```bash @@ -34,10 +36,12 @@ pip install pydglab ### 实例化并开启连接 +#### 对于郊狼2.0 + ```python import asyncio import pydglab -from pydglab import model +from pydglab import model_v2 async def _(): dglab_instance = pydglab.dglab() @@ -47,16 +51,26 @@ async def _(): asyncio.run(_()) ``` -## 文档 +#### 对于郊狼3.0 - 请查阅demo.py来获取更多信息。 +```python +import asyncio +import pydglab +from pydglab import model_v3 - 由于作者懒得写独立的文档了,因此关于模块的调用问题,请查阅内嵌的文档字符串(DocStrings)。 +async def _(): + dglab_instance = pydglab.dglab_v3() + await dglab_instance.create() + # Do whatever u want here + +asyncio.run(_()) +``` + +## 文档 - 目前仅支持郊狼2.0。 + 请查阅demo_v2.py或demo_v3.py(取决于你所连接的设备是郊狼2.0还是3.0)来获取更多信息。 - 3.0的支持是实验性的,对3.0的正式支持将在几天内更新,请查阅[fork](https://github.com/shilapi/DGLAB-python-driver/tree/v3)。 + 由于作者懒得写独立的文档了,因此关于模块的调用问题,请查阅内嵌的文档字符串(DocStrings)。 -## 接下来可能会实现的功能 + 目前已支持郊狼2.0与3.0! -- 让郊狼2.0实现与郊狼3.0相同的websocket远控。 diff --git a/demo_v2.py b/demo_v2.py index 4c66ca1..129868f 100644 --- a/demo_v2.py +++ b/demo_v2.py @@ -4,7 +4,7 @@ import logging import pydglab -from pydglab import model +from pydglab import model_v2 logging.basicConfig( format="%(module)s [%(levelname)s]: %(message)s", level=logging.INFO @@ -22,7 +22,7 @@ async def _(): await dglab_instance.get_strength() await dglab_instance.set_strength_sync(1, 1) await dglab_instance.set_wave_sync(0, 0, 0, 0, 0, 0) - await dglab_instance.set_wave_set(model.Wave_set["Going_Faster"], model.ChannelA) + await dglab_instance.set_wave_set(model_v2.Wave_set["Going_Faster"], model_v2.ChannelA) await dglab_instance.get_batterylevel() await dglab_instance.get_strength() await asyncio.sleep(2) diff --git a/pydglab/__init__.py b/pydglab/__init__.py index 64fcdb8..ac3b58e 100644 --- a/pydglab/__init__.py +++ b/pydglab/__init__.py @@ -11,7 +11,5 @@ _logger.addHandler(handler) from .service import dglab, dglab_v3 -from .model import * -from .bthandler import scan -from .model_v3 import * +from .bthandler_v2 import scan from .bthandler_v3 import scan diff --git a/pydglab/bthandler.py b/pydglab/bthandler_v2.py similarity index 88% rename from pydglab/bthandler.py rename to pydglab/bthandler_v2.py index e30af65..123e54b 100644 --- a/pydglab/bthandler.py +++ b/pydglab/bthandler_v2.py @@ -3,7 +3,7 @@ from typing import Tuple, List from bitstring import BitArray -from pydglab.model import * +from pydglab.model_v2 import * from pydglab.uuid import * logger = logging.getLogger(__name__) @@ -38,27 +38,28 @@ async def scan_(): return sorted(dglab_v2, key=lambda device: device[1])[0][0] -async def get_batterylevel_(client: BleakClient, characteristics: CoyoteV2 | CoyoteV3): +async def get_batterylevel_(client: BleakClient, characteristics: CoyoteV2): r = await client.read_gatt_char(characteristics.characteristicBattery) return r -async def get_strength_(client: BleakClient, characteristics: CoyoteV2 | CoyoteV3): +async def get_strength_(client: BleakClient, characteristics: CoyoteV2): r = await client.read_gatt_char(characteristics.characteristicEStimPower) # logger.debug(f"Received strenth bytes: {r.hex()} , which is {r}") r.reverse() r = BitArray(r).bin # logger.debug(f"Received strenth bytes after decoding: {r}") - return int(r[-22:-11], 2) / 11, int(r[-11:], 2) / 11 + return int(int(r[-22:-11], 2) / 2047 * 200), int(int(r[-11:], 2) / 2047 * 200) async def set_strength_( - client: BleakClient, value: Coyote, characteristics: CoyoteV2 | CoyoteV3 + client: BleakClient, value: Coyote, characteristics: CoyoteV2 ): # Create a byte array with the strength values. # The values are multiplied by 11 to convert them to the correct range. - strengthA = int(value.ChannelA.strength) * 11 - strengthB = int(value.ChannelB.strength) * 11 + # It seems that the max value is 2047, but the range is 0-200, so we divide by 2047 and multiply by + strengthA = int(int(value.ChannelA.strength) * 2047 / 200) + strengthB = int(int(value.ChannelB.strength) * 2047 / 200) if ( value.ChannelA.strength is None or value.ChannelA.strength < 0 @@ -85,7 +86,7 @@ async def set_strength_( async def set_wave_( client: BleakClient, value: ChannelA | ChannelB, - characteristics: CoyoteV2 | CoyoteV3, + characteristics: CoyoteV2, ): # Create a byte array with the wave values. array = ((value.waveZ << 15) + (value.waveY << 5) + value.waveX).to_bytes( diff --git a/pydglab/bthandler_v3.py b/pydglab/bthandler_v3.py index fbc583b..e2255f0 100644 --- a/pydglab/bthandler_v3.py +++ b/pydglab/bthandler_v3.py @@ -42,7 +42,9 @@ async def notify_(client: BleakClient, characteristics: CoyoteV3, callback: call await client.start_notify(characteristics.characteristicNotify, callback) -async def write_strenth_(client: BleakClient, value: Coyote_v3, characteristics: CoyoteV3): +async def write_strenth_( + client: BleakClient, value: Coyote, characteristics: CoyoteV3 +): struct = ( 0xB0, 0b00010000 + 0b00001111, @@ -65,7 +67,7 @@ async def write_strenth_(client: BleakClient, value: Coyote_v3, characteristics: async def write_coefficient_( - client: BleakClient, value: Coyote_v3, characteristics: CoyoteV3 + client: BleakClient, value: Coyote, characteristics: CoyoteV3 ): struct = ( 0xBF, diff --git a/pydglab/model.py b/pydglab/model_v2.py similarity index 90% rename from pydglab/model.py rename to pydglab/model_v2.py index 14360de..943dcbd 100644 --- a/pydglab/model.py +++ b/pydglab/model_v2.py @@ -21,8 +21,8 @@ def __init__(self): class Coyote(object): def __init__(self): - self.ChannelA: Optional[ChannelA] = ChannelA() - self.ChannelB: Optional[ChannelB] = ChannelB() + self.ChannelA: ChannelA = ChannelA() + self.ChannelB: ChannelB = ChannelB() self.Battery: Optional[int] = None @@ -44,4 +44,4 @@ def __init__(self): (1, 14, 20), (1, 9, 20), ], -} \ No newline at end of file +} diff --git a/pydglab/model_v3.py b/pydglab/model_v3.py index 435a72e..96f4a44 100644 --- a/pydglab/model_v3.py +++ b/pydglab/model_v3.py @@ -21,7 +21,7 @@ def __init__(self): self.limit: Optional[int] = None -class Coyote_v3(object): +class Coyote(object): def __init__(self): self.ChannelA: Optional[ChannelA] = ChannelA() self.ChannelB: Optional[ChannelB] = ChannelB() diff --git a/pydglab/service.py b/pydglab/service.py index 1ac38f7..8b5835b 100644 --- a/pydglab/service.py +++ b/pydglab/service.py @@ -1,16 +1,17 @@ import logging, asyncio, time from bleak import BleakClient from typing import Tuple -from pydglab.model import * -from pydglab.model_v3 import * +import pydglab.model_v2 as model_v2 +import pydglab.model_v3 as model_v3 from pydglab.uuid import * -import pydglab.bthandler as v2 +import pydglab.bthandler_v2 as v2 import pydglab.bthandler_v3 as v3 logger = logging.getLogger(__name__) + class dglab(object): - coyote = Coyote() + coyote = model_v2.Coyote() def __init__(self, address: str = None) -> None: self.address = address @@ -61,7 +62,7 @@ async def create(self) -> "dglab": self.characteristics.characteristicEStimB = i elif CoyoteV3.serviceWrite in service and CoyoteV3.serviceNotify in service: - raise Exception("DGLAB v3.0 is not supported") + raise Exception("DGLAB v3.0 found, please use dglab_v3 instead") else: raise Exception( "Unknown device (你自己看看你连的是什么jb设备)" @@ -80,8 +81,6 @@ async def create(self) -> "dglab": # Start the wave tasks, to keep the device functioning. self.wave_tasks = asyncio.gather( self._keep_wave(), - self._channelA_wave_set_handler(), - self._channelB_wave_set_handler(), ) return self @@ -131,7 +130,7 @@ async def get_strength(self) -> Tuple[int, int]: self.coyote.ChannelB.strength = int(value[1]) return self.coyote.ChannelA.strength, self.coyote.ChannelB.strength - async def set_strength(self, strength: int, channel: ChannelA | ChannelB) -> None: + async def set_strength(self, strength: int, channel: model_v2.ChannelA | model_v2.ChannelB) -> None: """ 设置电压强度。 额外设置这个函数用于单独调整强度只是为了和设置波形的函数保持一致罢了。 @@ -145,15 +144,15 @@ async def set_strength(self, strength: int, channel: ChannelA | ChannelB) -> Non int: 电压强度 """ - if channel is ChannelA: + if channel is model_v2.ChannelA: self.coyote.ChannelA.strength = strength - elif channel is ChannelB: + elif channel is model_v2.ChannelB: self.coyote.ChannelB.strength = strength r = await v2.set_strength_(self.client, self.coyote, self.characteristics) logger.debug(f"Set strength response: {r}") return ( self.coyote.ChannelA.strength - if channel is ChannelA + if channel is model_v2.ChannelA else self.coyote.ChannelB.strength ) @@ -185,7 +184,7 @@ async def set_strength_sync(self, strengthA: int, strengthB: int) -> None: """ async def set_wave_set( - self, wave_set: list[tuple[int, int, int]], channel: ChannelA | ChannelB + self, wave_set: list[tuple[int, int, int]], channel: model_v2.ChannelA | model_v2.ChannelB ) -> None: """ 设置波形组,也就是所谓“不断变化的波形”。 @@ -198,9 +197,9 @@ async def set_wave_set( Returns: None: None """ - if channel is ChannelA: + if channel is model_v2.ChannelA: self.channelA_wave_set = wave_set - elif channel is ChannelB: + elif channel is model_v2.ChannelB: self.channelB_wave_set = wave_set return None @@ -224,40 +223,6 @@ async def set_wave_set_sync( self.channelB_wave_set = wave_setB return None - async def _channelA_wave_set_handler(self) -> None: - """ - Do not use this function directly. - - Yep this is how wave set works :) - PR if you have a better solution. - """ - try: - while True: - for wave in self.channelA_wave_set: - self.coyote.ChannelA.waveX = wave[0] - self.coyote.ChannelA.waveY = wave[1] - self.coyote.ChannelA.waveZ = wave[2] - await asyncio.sleep(0.1) - except asyncio.exceptions.CancelledError: - pass - - async def _channelB_wave_set_handler(self) -> None: - """ - Do not use this function directly. - - Yep this is how wave set works :) - PR if you have a better solution. - """ - try: - while True: - for wave in self.channelB_wave_set: - self.coyote.ChannelB.waveX = wave[0] - self.coyote.ChannelB.waveY = wave[1] - self.coyote.ChannelB.waveZ = wave[2] - await asyncio.sleep(0.1) - except asyncio.exceptions.CancelledError: - pass - """ How set_wave works: Basically, it will generate a wave set with only one wave, @@ -266,7 +231,7 @@ async def _channelB_wave_set_handler(self) -> None: """ async def set_wave( - self, waveX: int, waveY: int, waveZ: int, channel: ChannelA | ChannelB + self, waveX: int, waveY: int, waveZ: int, channel: model_v2.ChannelA | model_v2.ChannelB ) -> Tuple[int, int, int]: """ 设置波形。 @@ -282,9 +247,9 @@ async def set_wave( Returns: Tuple[int, int, int]: 波形 """ - if channel is ChannelA: + if channel is model_v2.ChannelA: self.channelA_wave_set = [(waveX, waveY, waveZ)] - elif channel is ChannelB: + elif channel is model_v2.ChannelB: self.channelB_wave_set = [(waveX, waveY, waveZ)] return waveX, waveY, waveZ @@ -319,37 +284,59 @@ async def set_wave_sync( ), await v2.set_wave_(self.client, self.coyote.ChannelB, self.characteristics) return (waveX_A, waveY_A, waveZ_A), (waveX_B, waveY_B, waveZ_B) + def _channelA_wave_set_handler(self): + """ + Do not use this function directly. + + Yep this is how wave set works :) + PR if you have a better solution. + """ + while True: + for wave in self.channelA_wave_set: + self.coyote.ChannelA.waveX = wave[0] + self.coyote.ChannelA.waveY = wave[1] + self.coyote.ChannelA.waveZ = wave[2] + yield (None) + + def _channelB_wave_set_handler(self): + """ + Do not use this function directly. + + Yep this is how wave set works :) + PR if you have a better solution. + """ + while True: + for wave in self.channelB_wave_set: + self.coyote.ChannelB.waveX = wave[0] + self.coyote.ChannelB.waveY = wave[1] + self.coyote.ChannelB.waveZ = wave[2] + yield (None) + async def _keep_wave(self) -> None: """ Don't use this function directly. """ last_time = time.time() - last_time_local = time.time() - last_refreshing = ChannelA + + ChannelA_keeping = self._channelA_wave_set_handler() + ChannelB_keeping = self._channelB_wave_set_handler() + while True: try: # logger.debug(f"Time elapsed: {time.time() - last_time}") if time.time() - last_time >= 0.1: - + # Record time for loop last_time = time.time() - - # Refresh A and B channel by turn - - if self.coyote.ChannelA.strength == 0: - r = (0,0,0), await v2.set_wave_(self.client, self.coyote.ChannelB, self.characteristics) - elif self.coyote.ChannelB.strength == 0: - r = await v2.set_wave_(self.client, self.coyote.ChannelA, self.characteristics), (0,0,0) - else: - if last_refreshing == ChannelA: - r = (0,0,0), await v2.set_wave_(self.client, self.coyote.ChannelB, self.characteristics) - last_refreshing = ChannelB - elif last_refreshing == ChannelB: - r = await v2.set_wave_(self.client, self.coyote.ChannelA, self.characteristics), (0,0,0) - last_refreshing = ChannelA + + r = await v2.set_wave_( + self.client, self.coyote.ChannelA, self.characteristics + ), await v2.set_wave_( + self.client, self.coyote.ChannelB, self.characteristics + ) logger.debug(f"Set wave response: {r}") - logger.debug(f"Time elapsed: {time.time() - last_time_local}") - last_time_local = time.time() + next(ChannelA_keeping) + next(ChannelB_keeping) except asyncio.exceptions.CancelledError: logger.error("Cancelled error") break @@ -373,7 +360,7 @@ async def close(self): class dglab_v3(object): - coyote = Coyote_v3() + coyote = model_v3.Coyote() def __init__(self, address: str = None) -> None: self.address = address @@ -408,7 +395,7 @@ async def create(self) -> "dglab_v3": service = [service.uuid for service in services] logger.debug(f"Got services: {str(service)}") if CoyoteV2.serviceBattery in service and CoyoteV2.serviceEStim in service: - raise Exception("Use dglab_v2 instead") + raise Exception("DGLAB v2.0 found, please use dglab instead") elif CoyoteV3.serviceWrite in service and CoyoteV3.serviceNotify in service: logger.info("Connected to DGLAB v3.0") @@ -439,9 +426,9 @@ async def create(self) -> "dglab_v3": self.coyote.ChannelB.coefficientStrenth = 100 self.coyote.ChannelA.coefficientFrequency = 100 self.coyote.ChannelB.coefficientFrequency = 100 - - await self.set_coefficient(200, 100, 100, ChannelA) - await self.set_coefficient(200, 100, 100, ChannelB) + + await self.set_coefficient(200, 100, 100, model_v3.ChannelA) + await self.set_coefficient(200, 100, 100, model_v3.ChannelB) await self.set_wave_sync(0, 0, 0, 0, 0, 0) await self.set_strength_sync(0, 0) @@ -493,7 +480,7 @@ async def get_strength(self) -> Tuple[int, int]: """ return self.coyote.ChannelA.strength, self.coyote.ChannelB.strength - async def set_strength(self, strength: int, channel: ChannelA | ChannelB) -> None: + async def set_strength(self, strength: int, channel: model_v3.ChannelA | model_v3.ChannelB) -> None: """ 设置电压强度。 额外设置这个函数用于单独调整强度只是为了和设置波形的函数保持一致罢了。 @@ -507,17 +494,23 @@ async def set_strength(self, strength: int, channel: ChannelA | ChannelB) -> Non int: 电压强度 """ - if channel is ChannelA: + if channel is model_v3.ChannelA: self.coyote.ChannelA.strength = strength - elif channel is ChannelB: + elif channel is model_v3.ChannelB: self.coyote.ChannelB.strength = strength return ( self.coyote.ChannelA.strength - if channel is ChannelA + if channel is model_v3.ChannelA else self.coyote.ChannelB.strength ) - async def set_coefficient(self, strength_limit: int, strength_coefficient: int, frequency_coefficient: int, channel: ChannelA | ChannelB) -> None: + async def set_coefficient( + self, + strength_limit: int, + strength_coefficient: int, + frequency_coefficient: int, + channel: model_v3.ChannelA | model_v3.ChannelB, + ) -> None: """ 设置强度上线与平衡常数。 Set the strength limit and coefficient of the device. @@ -532,21 +525,29 @@ async def set_coefficient(self, strength_limit: int, strength_coefficient: int, Tuple[int, int, int]: 电压强度上限,强度平衡常数,频率平衡常数 """ - if channel is ChannelA: + if channel is model_v3.ChannelA: self.coyote.ChannelA.limit = strength_limit self.coyote.ChannelA.coefficientStrenth = strength_coefficient self.coyote.ChannelA.coefficientFrequency = frequency_coefficient - elif channel is ChannelB: + elif channel is model_v3.ChannelB: self.coyote.ChannelB.limit = strength_limit self.coyote.ChannelB.coefficientStrenth = strength_coefficient self.coyote.ChannelB.coefficientFrequency = frequency_coefficient - + await v3.write_coefficient_(self.client, self.coyote, self.characteristics) - + return ( - (self.coyote.ChannelA.limit, self.coyote.ChannelA.coefficientStrenth, self.coyote.ChannelA.coefficientFrequency) - if channel is ChannelA - else (self.coyote.ChannelB.limit, self.coyote.ChannelB.coefficientStrenth, self.coyote.ChannelB.coefficientFrequency) + ( + self.coyote.ChannelA.limit, + self.coyote.ChannelA.coefficientStrenth, + self.coyote.ChannelA.coefficientFrequency, + ) + if channel is model_v3.ChannelA + else ( + self.coyote.ChannelB.limit, + self.coyote.ChannelB.coefficientStrenth, + self.coyote.ChannelB.coefficientFrequency, + ) ) async def set_strength_sync(self, strengthA: int, strengthB: int) -> None: @@ -575,7 +576,7 @@ async def set_strength_sync(self, strengthA: int, strengthB: int) -> None: """ async def set_wave_set( - self, wave_set: list[tuple[int, int, int]], channel: ChannelA | ChannelB + self, wave_set: list[tuple[int, int, int]], channel: model_v3.ChannelA | model_v3.ChannelB ) -> None: """ 设置波形组,也就是所谓“不断变化的波形”。 @@ -588,9 +589,9 @@ async def set_wave_set( Returns: None: None """ - if channel is ChannelA: + if channel is model_v3.ChannelA: self.channelA_wave_set = wave_set - elif channel is ChannelB: + elif channel is model_v3.ChannelB: self.channelB_wave_set = wave_set return None @@ -633,7 +634,7 @@ def waveset_converter( """ async def set_wave( - self, waveX: int, waveY: int, waveZ: int, channel: ChannelA | ChannelB + self, waveX: int, waveY: int, waveZ: int, channel: model_v3.ChannelA | model_v3.ChannelB ) -> Tuple[int, int, int]: """ 设置波形。 @@ -649,9 +650,9 @@ async def set_wave( Returns: Tuple[int, int, int]: 波形 """ - if channel is ChannelA: + if channel is model_v3.ChannelA: self.channelA_wave_set = [(waveX, waveY, waveZ)] - elif channel is ChannelB: + elif channel is model_v3.ChannelB: self.channelB_wave_set = [(waveX, waveY, waveZ)] return waveX, waveY, waveZ @@ -683,7 +684,7 @@ async def set_wave_sync( self.channelB_wave_set = [(waveX_B, waveY_B, waveZ_B)] return (waveX_A, waveY_A, waveZ_A), (waveX_B, waveY_B, waveZ_B) - def _channelA_wave_set_handler(self) -> None: + def _channelA_wave_set_handler(self): """ Do not use this function directly. @@ -694,15 +695,15 @@ def _channelA_wave_set_handler(self) -> None: while True: for wave in self.channelA_wave_set: wave = self.waveset_converter(wave) - self.coyote.ChannelA.wave.insert(0,wave[0]) + self.coyote.ChannelA.wave.insert(0, wave[0]) self.coyote.ChannelA.wave.pop() - self.coyote.ChannelA.waveStrenth.insert(0,wave[1]) + self.coyote.ChannelA.waveStrenth.insert(0, wave[1]) self.coyote.ChannelA.waveStrenth.pop() - yield(None) + yield (None) except asyncio.exceptions.CancelledError: pass - def _channelB_wave_set_handler(self) -> None: + def _channelB_wave_set_handler(self): """ Do not use this function directly. @@ -713,11 +714,11 @@ def _channelB_wave_set_handler(self) -> None: while True: for wave in self.channelB_wave_set: wave = self.waveset_converter(wave) - self.coyote.ChannelB.wave.insert(0,wave[0]) + self.coyote.ChannelB.wave.insert(0, wave[0]) self.coyote.ChannelB.wave.pop() - self.coyote.ChannelB.waveStrenth.insert(0,wave[1]) + self.coyote.ChannelB.waveStrenth.insert(0, wave[1]) self.coyote.ChannelB.waveStrenth.pop() - yield(None) + yield (None) except asyncio.exceptions.CancelledError: pass @@ -727,20 +728,24 @@ async def _retainer(self) -> None: """ ChannelA_keeping = self._channelA_wave_set_handler() ChannelB_keeping = self._channelB_wave_set_handler() - + last_time = time.time() - + while True: if time.time() - last_time >= 0.1: - + # Record time for loop last_time = time.time() - logger.debug(f"Using wave: {self.coyote.ChannelA.wave}, {self.coyote.ChannelA.waveStrenth}, {self.coyote.ChannelB.wave}, {self.coyote.ChannelB.waveStrenth}") - r = await v3.write_strenth_(self.client, self.coyote, self.characteristics) + logger.debug( + f"Using wave: {self.coyote.ChannelA.wave}, {self.coyote.ChannelA.waveStrenth}, {self.coyote.ChannelB.wave}, {self.coyote.ChannelB.waveStrenth}" + ) + r = await v3.write_strenth_( + self.client, self.coyote, self.characteristics + ) logger.debug(f"Retainer response: {r}") next(ChannelA_keeping) next(ChannelB_keeping) - + return None async def close(self) -> None: diff --git a/pyproject.toml b/pyproject.toml index 98b4a8a..4cfd581 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pydglab" -version = "0.4.2" +version = "1.0.0" description = "A Third-party DGLAB Python Driver" authors = ["Shilapi "] license = "GNU GPLv3"