From 70e8a40ed27eed4ff44c28cc2b88c63e2266146a Mon Sep 17 00:00:00 2001 From: Michael Arthur Date: Thu, 19 Sep 2024 09:20:02 +1200 Subject: [PATCH] Fix issues around restoring data and state not being shared correctly --- pymammotion/data/model/device.py | 2 +- pymammotion/data/model/rapid_state.py | 4 +- pymammotion/data/model/report_info.py | 16 +++---- .../mammotion/commands/mammotion_command.py | 26 ++++++++++- pymammotion/mammotion/devices/base.py | 43 +++++-------------- pymammotion/mammotion/devices/mammotion.py | 21 ++++++--- .../mammotion/devices/mammotion_bluetooth.py | 3 +- .../mammotion/devices/mammotion_cloud.py | 1 - pymammotion/mqtt/mammotion_mqtt.py | 1 + pyproject.toml | 6 +-- 10 files changed, 66 insertions(+), 57 deletions(-) diff --git a/pymammotion/data/model/device.py b/pymammotion/data/model/device.py index 4627ba9..0d69fd9 100644 --- a/pymammotion/data/model/device.py +++ b/pymammotion/data/model/device.py @@ -109,7 +109,7 @@ def update_report_data(self, toapp_report_data: ReportInfoData) -> None: location.zone_hash if self.report_data.dev.sys_status == WorkMode.MODE_WORKING else 0 ) - self.report_data = self.report_data.from_dict(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE)) + self.report_data.update(toapp_report_data.to_dict(casing=betterproto.Casing.SNAKE)) def run_state_update(self, rapid_state: SystemRapidStateTunnelMsg) -> None: coordinate_converter = CoordinateConverter(self.location.RTK.latitude, self.location.RTK.longitude) diff --git a/pymammotion/data/model/rapid_state.py b/pymammotion/data/model/rapid_state.py index 1bf4302..201b6d0 100644 --- a/pymammotion/data/model/rapid_state.py +++ b/pymammotion/data/model/rapid_state.py @@ -1,6 +1,8 @@ from dataclasses import dataclass from enum import Enum +from mashumaro.mixins.orjson import DataClassORJSONMixin + from pymammotion.utility.conversions import parse_double @@ -11,7 +13,7 @@ class RTKStatus(Enum): @dataclass -class RapidState: +class RapidState(DataClassORJSONMixin): pos_x: float = 0 pos_y: float = 0 rtk_status: RTKStatus = RTKStatus.NONE diff --git a/pymammotion/data/model/report_info.py b/pymammotion/data/model/report_info.py index 0810b85..af80671 100644 --- a/pymammotion/data/model/report_info.py +++ b/pymammotion/data/model/report_info.py @@ -63,22 +63,20 @@ class WorkData(DataClassORJSONMixin): @dataclass -class ReportData: +class ReportData(DataClassORJSONMixin): connect: ConnectData = field(default_factory=ConnectData) dev: DeviceData = field(default_factory=DeviceData) rtk: RTKData = field(default_factory=RTKData) locations: list[LocationData] = field(default_factory=list) work: WorkData = field(default_factory=WorkData) - def from_dict(self, data: dict): + def update(self, data: dict) -> None: locations = self.locations if data.get("locations") is not None: locations = [LocationData.from_dict(loc) for loc in data.get("locations", [])] - return ReportData( - connect=ConnectData.from_dict(data.get("connect", asdict(self.connect))), - dev=DeviceData.from_dict(data.get("dev", asdict(self.dev))), - rtk=RTKData.from_dict(data.get("rtk", asdict(self.rtk))), - locations=locations, - work=WorkData.from_dict(data.get("work", asdict(self.work))), - ) + self.connect = ConnectData.from_dict(data.get("connect", asdict(self.connect))) + self.dev = DeviceData.from_dict(data.get("dev", asdict(self.dev))) + self.rtk = RTKData.from_dict(data.get("rtk", asdict(self.rtk))) + self.locations = locations + self.work = WorkData.from_dict(data.get("work", asdict(self.work))) diff --git a/pymammotion/mammotion/commands/mammotion_command.py b/pymammotion/mammotion/commands/mammotion_command.py index dc29efb..cf2e03c 100644 --- a/pymammotion/mammotion/commands/mammotion_command.py +++ b/pymammotion/mammotion/commands/mammotion_command.py @@ -4,7 +4,7 @@ from pymammotion.mammotion.commands.messages.ota import MessageOta from pymammotion.mammotion.commands.messages.system import MessageSystem from pymammotion.mammotion.commands.messages.video import MessageVideo -from pymammotion.proto import dev_net_pb2, luba_msg_pb2 +from pymammotion.utility.movement import get_percent, transform_both_speeds class MammotionCommand(MessageSystem, MessageNavigation, MessageNetwork, MessageOta, MessageVideo, MessageDriver): @@ -23,3 +23,27 @@ def get_device_product_key(self) -> str: def set_device_product_key(self, product_key: str) -> None: self._product_key = product_key + + async def move_forward(self, linear: float) -> None: + """Move forward. values 0.0 1.0.""" + linear_percent = get_percent(abs(linear * 100)) + (linear_speed, angular_speed) = transform_both_speeds(90.0, 0.0, linear_percent, 0.0) + await self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) + + async def move_back(self, linear: float) -> None: + """Move back. values 0.0 1.0.""" + linear_percent = get_percent(abs(linear * 100)) + (linear_speed, angular_speed) = transform_both_speeds(270.0, 0.0, linear_percent, 0.0) + await self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) + + async def move_left(self, angular: float) -> None: + """Move forward. values 0.0 1.0.""" + angular_percent = get_percent(abs(angular * 100)) + (linear_speed, angular_speed) = transform_both_speeds(0.0, 0.0, 0.0, angular_percent) + await self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) + + async def move_right(self, angular: float) -> None: + """Move back. values 0.0 1.0.""" + angular_percent = get_percent(abs(angular * 100)) + (linear_speed, angular_speed) = transform_both_speeds(0.0, 180.0, 0.0, angular_percent) + await self.send_movement(linear_speed=linear_speed, angular_speed=angular_speed) diff --git a/pymammotion/mammotion/devices/base.py b/pymammotion/mammotion/devices/base.py index 683b90b..bdcfde3 100644 --- a/pymammotion/mammotion/devices/base.py +++ b/pymammotion/mammotion/devices/base.py @@ -12,7 +12,6 @@ from pymammotion.proto import has_field from pymammotion.proto.luba_msg import LubaMsg from pymammotion.proto.mctrl_nav import NavGetCommDataAck, NavGetHashListAck -from pymammotion.utility.movement import get_percent, transform_both_speeds _LOGGER = logging.getLogger(__name__) @@ -35,7 +34,6 @@ def find_next_integer(lst: list[int], current_hash: int) -> int | None: class MammotionBaseDevice: """Base class for Mammotion devices.""" - _mower: MowingDevice _state_manager: StateManager _cloud_device: Device | None = None @@ -43,8 +41,7 @@ def __init__(self, device: MowingDevice, cloud_device: Device | None = None) -> """Initialize MammotionBaseDevice.""" self.loop = asyncio.get_event_loop() self._raw_data = LubaMsg().to_dict(casing=betterproto.Casing.SNAKE) - self._mower = device - self._state_manager = StateManager(self._mower) + self._state_manager = StateManager(device) self._state_manager.gethash_ack_callback = self.datahash_response self._state_manager.get_commondata_ack_callback = self.commdata_response self._notify_future: asyncio.Future[bytes] | None = None @@ -65,11 +62,11 @@ async def commdata_response(self, common_data: NavGetCommDataAck) -> None: total_frame = common_data.total_frame current_frame = common_data.current_frame - missing_frames = self._mower.map.missing_frame(common_data) + missing_frames = self.mower.map.missing_frame(common_data) if len(missing_frames) == 0: # get next in hash ack list - data_hash = find_next_integer(self._mower.nav.toapp_gethash_ack.data_couple, common_data.hash) + data_hash = find_next_integer(self.mower.nav.toapp_gethash_ack.data_couple, common_data.hash) if data_hash is None: return @@ -104,7 +101,7 @@ def _update_raw_data(self, data: bytes) -> None: case "ota": self._update_ota_data(tmp_msg) - self._mower.update_raw(self._raw_data) + self.mower.update_raw(self._raw_data) def _update_nav_data(self, tmp_msg) -> None: """Update navigation data.""" @@ -180,7 +177,7 @@ def raw_data(self) -> dict[str, Any]: @property def mower(self) -> MowingDevice: """Get the LubaMsg of the device.""" - return self._mower + return self._state_manager.get_device() @abstractmethod async def queue_command(self, key: str, **kwargs: any) -> bytes | None: @@ -207,7 +204,7 @@ async def start_map_sync(self) -> None: try: # work out why this crashes sometimes for better proto - if self._cloud_device and len(self._mower.map.area_name) == 0: + if self._cloud_device and len(self.mower.map.area_name) == 0: await self.queue_command("get_area_name_list", device_id=self._cloud_device.iotId) except Exception: """Do nothing for now.""" @@ -231,30 +228,10 @@ async def async_get_errors(self) -> None: await self.queue_command("allpowerfull_rw", id=5, rw=1, context=2) await self.queue_command("allpowerfull_rw", id=5, rw=1, context=3) - async def move_forward(self, linear: float) -> None: - """Move forward. values 0.0 1.0.""" - linear_percent = get_percent(abs(linear * 100)) - (linear_speed, angular_speed) = transform_both_speeds(90.0, 0.0, linear_percent, 0.0) - await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed) - - async def move_back(self, linear: float) -> None: - """Move back. values 0.0 1.0.""" - linear_percent = get_percent(abs(linear * 100)) - (linear_speed, angular_speed) = transform_both_speeds(270.0, 0.0, linear_percent, 0.0) - await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed) - - async def move_left(self, angulur: float) -> None: - """Move forward. values 0.0 1.0.""" - angular_percent = get_percent(abs(angulur * 100)) - (linear_speed, angular_speed) = transform_both_speeds(0.0, 0.0, 0.0, angular_percent) - await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed) - - async def move_right(self, angulur: float) -> None: - """Move back. values 0.0 1.0.""" - angular_percent = get_percent(abs(angulur * 100)) - (linear_speed, angular_speed) = transform_both_speeds(0.0, 180.0, 0.0, angular_percent) - await self.queue_command("send_movement", linear_speed=linear_speed, angular_speed=angular_speed) - async def command(self, key: str, **kwargs): """Send a command to the device.""" return await self.queue_command(key, **kwargs) + + @property + def state_manager(self): + return self._state_manager diff --git a/pymammotion/mammotion/devices/mammotion.py b/pymammotion/mammotion/devices/mammotion.py index 7347cce..f483a18 100644 --- a/pymammotion/mammotion/devices/mammotion.py +++ b/pymammotion/mammotion/devices/mammotion.py @@ -35,7 +35,6 @@ class ConnectionPreference(Enum): class MammotionMixedDeviceManager: _ble_device: MammotionBaseBLEDevice | None = None _cloud_device: MammotionBaseCloudDevice | None = None - _mowing_state: MowingDevice preference: ConnectionPreference def __init__( @@ -46,15 +45,25 @@ def __init__( mqtt: MammotionCloud | None = None, preference: ConnectionPreference = ConnectionPreference.BLUETOOTH, ) -> None: + self._mower_state = None self.name = name self._mowing_state = MowingDevice() self.add_ble(ble_device) self.add_cloud(cloud_device, mqtt) self.preference = preference + @property def mower_state(self): return self._mowing_state + @mower_state.setter + def mower_state(self, value: MowingDevice) -> None: + if self._cloud_device: + self._cloud_device.state_manager.set_device(value) + if self._ble_device: + self._ble_device.state_manager.set_device(value) + self._mowing_state = value + def ble(self) -> MammotionBaseBLEDevice | None: return self._ble_device @@ -63,12 +72,12 @@ def cloud(self) -> MammotionBaseCloudDevice | None: def add_ble(self, ble_device: BLEDevice) -> None: if ble_device is not None: - self._ble_device = MammotionBaseBLEDevice(self._mowing_state, ble_device) + self._ble_device = MammotionBaseBLEDevice(self.mower_state, ble_device) def add_cloud(self, cloud_device: Device | None = None, mqtt: MammotionCloud | None = None) -> None: if cloud_device is not None: self._cloud_device = MammotionBaseCloudDevice( - mqtt, cloud_device=cloud_device, mowing_state=self._mowing_state + mqtt, cloud_device=cloud_device, mowing_state=self.mower_state ) def replace_cloud(self, cloud_device: MammotionBaseCloudDevice) -> None: @@ -284,13 +293,13 @@ async def start_map_sync(self, name: str): async def get_stream_subscription(self, name: str): device = self.get_device_by_name(name) - if self._preference is ConnectionPreference.WIFI: + if device.preference is ConnectionPreference.WIFI: if device.has_cloud(): - _stream_response = await self.cloud().cloud_client.get_stream_subscription(device.cloud().iot_id) + _stream_response = await device.cloud().mqtt.cloud_client.get_stream_subscription(device.cloud().iot_id) _LOGGER.debug(_stream_response) return _stream_response def mower(self, name: str): device = self.get_device_by_name(name) if device: - return device.mower_state() + return device.mower_state diff --git a/pymammotion/mammotion/devices/mammotion_bluetooth.py b/pymammotion/mammotion/devices/mammotion_bluetooth.py index 4ba4960..2b1f5c0 100644 --- a/pymammotion/mammotion/devices/mammotion_bluetooth.py +++ b/pymammotion/mammotion/devices/mammotion_bluetooth.py @@ -77,7 +77,6 @@ def __init__(self, mowing_state: MowingDevice, device: BLEDevice, interface: int self._prev_notification = None self._interface = f"hci{interface}" self._device = device - self._mower = mowing_state self._client: BleakClientWithServiceCache | None = None self._read_char: BleakGATTCharacteristic | int | str | UUID = 0 self._write_char: BleakGATTCharacteristic | int | str | UUID = 0 @@ -191,7 +190,7 @@ def name(self) -> str: def rssi(self) -> int: """Return RSSI of device.""" try: - return cast(self._mower.sys.toapp_report_data.connect.ble_rssi, int) + return cast(self.mower.sys.toapp_report_data.connect.ble_rssi, int) finally: return 0 diff --git a/pymammotion/mammotion/devices/mammotion_cloud.py b/pymammotion/mammotion/devices/mammotion_cloud.py index b236f9a..8528a08 100644 --- a/pymammotion/mammotion/devices/mammotion_cloud.py +++ b/pymammotion/mammotion/devices/mammotion_cloud.py @@ -155,7 +155,6 @@ def __init__(self, mqtt: MammotionCloud, cloud_device: Device, mowing_state: Mow self._mqtt = mqtt self.iot_id = cloud_device.iotId self.device = cloud_device - self._mower = mowing_state self._command_futures = {} self._commands: MammotionCommand = MammotionCommand(cloud_device.deviceName) self.currentID = "" diff --git a/pymammotion/mqtt/mammotion_mqtt.py b/pymammotion/mqtt/mammotion_mqtt.py index d1d0265..29bca69 100644 --- a/pymammotion/mqtt/mammotion_mqtt.py +++ b/pymammotion/mqtt/mammotion_mqtt.py @@ -91,6 +91,7 @@ def connect_async(self) -> None: def disconnect(self) -> None: """Disconnect from MQTT Server.""" logger.info("Disconnecting...") + self._linkkit_client.disconnect() def _thing_on_thing_enable(self, user_data) -> None: diff --git a/pyproject.toml b/pyproject.toml index 3d78ac0..99a0013 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] name = "pymammotion" -version = "0.2.41" +version = "0.2.43" [tool.poetry] name = "pymammotion" -version = "0.2.41" +version = "0.2.43" license = "GNU-3.0" description = "" readme = "README.md" @@ -55,7 +55,7 @@ mypy = "^1.10.0" pre-commit = "^3.8.0" [tool.bumpver] -current_version = "0.2.41" +current_version = "0.2.43" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "Bump version {old_version} -> {new_version}" commit = true