python-roborock 2.40.1__tar.gz → 2.41.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_roborock-2.40.1 → python_roborock-2.41.1}/PKG-INFO +1 -1
- {python_roborock-2.40.1 → python_roborock-2.41.1}/pyproject.toml +1 -1
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/containers.py +15 -14
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/device_manager.py +2 -0
- python_roborock-2.41.1/roborock/devices/traits/dnd.py +41 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/traits/status.py +3 -3
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/v1_channel.py +8 -4
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/v1_rpc_channel.py +19 -16
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/protocols/v1_protocol.py +3 -1
- {python_roborock-2.40.1 → python_roborock-2.41.1}/LICENSE +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/README.md +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/__init__.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/api.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/b01_containers.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/callbacks.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/clean_modes.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/cli.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/cloud_api.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/code_mappings.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/command_cache.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/const.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/device_features.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/README.md +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/__init__.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/cache.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/channel.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/device.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/local_channel.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/traits/b01/props.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/traits/dyad.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/traits/trait.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/devices/traits/zeo.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/exceptions.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/mqtt/session.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/protocol.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/py.typed +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/roborock_future.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/roborock_message.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/util.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/web_api.py +0 -0
|
@@ -109,8 +109,6 @@ def _decamelize(s: str):
|
|
|
109
109
|
|
|
110
110
|
@dataclass
|
|
111
111
|
class RoborockBase:
|
|
112
|
-
_ignore_keys = [] # type: ignore
|
|
113
|
-
|
|
114
112
|
@staticmethod
|
|
115
113
|
def _convert_to_class_obj(class_type: type, value):
|
|
116
114
|
if get_origin(class_type) is list:
|
|
@@ -134,8 +132,8 @@ class RoborockBase:
|
|
|
134
132
|
return None
|
|
135
133
|
field_types = {field.name: field.type for field in dataclasses.fields(cls)}
|
|
136
134
|
result: dict[str, Any] = {}
|
|
137
|
-
for
|
|
138
|
-
key = _decamelize(
|
|
135
|
+
for orig_key, value in data.items():
|
|
136
|
+
key = _decamelize(orig_key)
|
|
139
137
|
if (field_type := field_types.get(key)) is None:
|
|
140
138
|
continue
|
|
141
139
|
if value == "None" or value is None:
|
|
@@ -178,16 +176,18 @@ class RoborockBaseTimer(RoborockBase):
|
|
|
178
176
|
end_hour: int | None = None
|
|
179
177
|
end_minute: int | None = None
|
|
180
178
|
enabled: int | None = None
|
|
181
|
-
start_time: datetime.time | None = None
|
|
182
|
-
end_time: datetime.time | None = None
|
|
183
179
|
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
@property
|
|
181
|
+
def start_time(self) -> datetime.time | None:
|
|
182
|
+
return (
|
|
186
183
|
datetime.time(hour=self.start_hour, minute=self.start_minute)
|
|
187
184
|
if self.start_hour is not None and self.start_minute is not None
|
|
188
185
|
else None
|
|
189
186
|
)
|
|
190
|
-
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def end_time(self) -> datetime.time | None:
|
|
190
|
+
return (
|
|
191
191
|
datetime.time(hour=self.end_hour, minute=self.end_minute)
|
|
192
192
|
if self.end_hour is not None and self.end_minute is not None
|
|
193
193
|
else None
|
|
@@ -684,19 +684,20 @@ class MultiMapsListMapInfoBakMaps(RoborockBase):
|
|
|
684
684
|
|
|
685
685
|
@dataclass
|
|
686
686
|
class MultiMapsListMapInfo(RoborockBase):
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
mapFlag: int
|
|
687
|
+
map_flag: int
|
|
690
688
|
name: str
|
|
691
689
|
add_time: Any | None = None
|
|
692
690
|
length: Any | None = None
|
|
693
691
|
bak_maps: list[MultiMapsListMapInfoBakMaps] | None = None
|
|
694
692
|
|
|
693
|
+
@property
|
|
694
|
+
def mapFlag(self) -> int:
|
|
695
|
+
"""Alias for map_flag, returns the map flag as an integer."""
|
|
696
|
+
return self.map_flag
|
|
697
|
+
|
|
695
698
|
|
|
696
699
|
@dataclass
|
|
697
700
|
class MultiMapsList(RoborockBase):
|
|
698
|
-
_ignore_keys = ["mapFlag"]
|
|
699
|
-
|
|
700
701
|
max_multi_map: int | None = None
|
|
701
702
|
max_bak_map: int | None = None
|
|
702
703
|
multi_map_count: int | None = None
|
|
@@ -22,6 +22,7 @@ from .cache import Cache, NoCache
|
|
|
22
22
|
from .channel import Channel
|
|
23
23
|
from .mqtt_channel import create_mqtt_channel
|
|
24
24
|
from .traits.b01.props import B01PropsApi
|
|
25
|
+
from .traits.dnd import DoNotDisturbTrait
|
|
25
26
|
from .traits.dyad import DyadApi
|
|
26
27
|
from .traits.status import StatusTrait
|
|
27
28
|
from .traits.trait import Trait
|
|
@@ -152,6 +153,7 @@ async def create_device_manager(
|
|
|
152
153
|
case DeviceVersion.V1:
|
|
153
154
|
channel = create_v1_channel(user_data, mqtt_params, mqtt_session, device, cache)
|
|
154
155
|
traits.append(StatusTrait(product, channel.rpc_channel))
|
|
156
|
+
traits.append(DoNotDisturbTrait(channel.rpc_channel))
|
|
155
157
|
case DeviceVersion.A01:
|
|
156
158
|
mqtt_channel = create_mqtt_channel(user_data, mqtt_params, mqtt_session, device)
|
|
157
159
|
match product.category:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Module for Roborock V1 devices.
|
|
2
|
+
|
|
3
|
+
This interface is experimental and subject to breaking changes without notice
|
|
4
|
+
until the API is stable.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from roborock.containers import DnDTimer
|
|
10
|
+
from roborock.devices.v1_rpc_channel import V1RpcChannel
|
|
11
|
+
from roborock.roborock_typing import RoborockCommand
|
|
12
|
+
|
|
13
|
+
from .trait import Trait
|
|
14
|
+
|
|
15
|
+
_LOGGER = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"DoNotDisturbTrait",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DoNotDisturbTrait(Trait):
|
|
23
|
+
"""Trait for managing Do Not Disturb (DND) settings on Roborock devices."""
|
|
24
|
+
|
|
25
|
+
name = "do_not_disturb"
|
|
26
|
+
|
|
27
|
+
def __init__(self, rpc_channel: V1RpcChannel) -> None:
|
|
28
|
+
"""Initialize the DoNotDisturbTrait."""
|
|
29
|
+
self._rpc_channel = rpc_channel
|
|
30
|
+
|
|
31
|
+
async def get_dnd_timer(self) -> DnDTimer:
|
|
32
|
+
"""Get the current Do Not Disturb (DND) timer settings of the device."""
|
|
33
|
+
return await self._rpc_channel.send_command(RoborockCommand.GET_DND_TIMER, response_type=DnDTimer)
|
|
34
|
+
|
|
35
|
+
async def set_dnd_timer(self, dnd_timer: DnDTimer) -> None:
|
|
36
|
+
"""Set the Do Not Disturb (DND) timer settings of the device."""
|
|
37
|
+
await self._rpc_channel.send_command(RoborockCommand.SET_DND_TIMER, params=dnd_timer.as_dict())
|
|
38
|
+
|
|
39
|
+
async def clear_dnd_timer(self) -> None:
|
|
40
|
+
"""Clear the Do Not Disturb (DND) timer settings of the device."""
|
|
41
|
+
await self._rpc_channel.send_command(RoborockCommand.CLOSE_DND_TIMER)
|
|
@@ -12,20 +12,20 @@ from roborock.containers import (
|
|
|
12
12
|
S7MaxVStatus,
|
|
13
13
|
Status,
|
|
14
14
|
)
|
|
15
|
+
from roborock.devices.v1_rpc_channel import V1RpcChannel
|
|
15
16
|
from roborock.roborock_typing import RoborockCommand
|
|
16
17
|
|
|
17
|
-
from ..v1_rpc_channel import V1RpcChannel
|
|
18
18
|
from .trait import Trait
|
|
19
19
|
|
|
20
20
|
_LOGGER = logging.getLogger(__name__)
|
|
21
21
|
|
|
22
22
|
__all__ = [
|
|
23
|
-
"
|
|
23
|
+
"StatusTrait",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class StatusTrait(Trait):
|
|
28
|
-
"""
|
|
28
|
+
"""Trait for managing the status of Roborock devices."""
|
|
29
29
|
|
|
30
30
|
name = "status"
|
|
31
31
|
|
|
@@ -22,7 +22,7 @@ from .cache import Cache
|
|
|
22
22
|
from .channel import Channel
|
|
23
23
|
from .local_channel import LocalChannel, LocalSession, create_local_session
|
|
24
24
|
from .mqtt_channel import MqttChannel
|
|
25
|
-
from .v1_rpc_channel import V1RpcChannel,
|
|
25
|
+
from .v1_rpc_channel import PickFirstAvailable, V1RpcChannel, create_local_rpc_channel, create_mqtt_rpc_channel
|
|
26
26
|
|
|
27
27
|
_LOGGER = logging.getLogger(__name__)
|
|
28
28
|
|
|
@@ -60,7 +60,11 @@ class V1Channel(Channel):
|
|
|
60
60
|
self._mqtt_rpc_channel = create_mqtt_rpc_channel(mqtt_channel, security_data)
|
|
61
61
|
self._local_session = local_session
|
|
62
62
|
self._local_channel: LocalChannel | None = None
|
|
63
|
-
self.
|
|
63
|
+
self._local_rpc_channel: V1RpcChannel | None = None
|
|
64
|
+
# Prefer local, fallback to MQTT
|
|
65
|
+
self._combined_rpc_channel = PickFirstAvailable(
|
|
66
|
+
[lambda: self._local_rpc_channel, lambda: self._mqtt_rpc_channel]
|
|
67
|
+
)
|
|
64
68
|
self._mqtt_unsub: Callable[[], None] | None = None
|
|
65
69
|
self._local_unsub: Callable[[], None] | None = None
|
|
66
70
|
self._callback: Callable[[RoborockMessage], None] | None = None
|
|
@@ -84,7 +88,7 @@ class V1Channel(Channel):
|
|
|
84
88
|
@property
|
|
85
89
|
def rpc_channel(self) -> V1RpcChannel:
|
|
86
90
|
"""Return the combined RPC channel prefers local with a fallback to MQTT."""
|
|
87
|
-
return self._combined_rpc_channel
|
|
91
|
+
return self._combined_rpc_channel
|
|
88
92
|
|
|
89
93
|
@property
|
|
90
94
|
def mqtt_rpc_channel(self) -> V1RpcChannel:
|
|
@@ -160,7 +164,7 @@ class V1Channel(Channel):
|
|
|
160
164
|
except RoborockException as e:
|
|
161
165
|
self._local_channel = None
|
|
162
166
|
raise RoborockException(f"Error connecting to local device {self._device_uid}: {e}") from e
|
|
163
|
-
self.
|
|
167
|
+
self._local_rpc_channel = create_local_rpc_channel(self._local_channel)
|
|
164
168
|
return await self._local_channel.subscribe(self._on_local_message)
|
|
165
169
|
|
|
166
170
|
def _on_mqtt_message(self, message: RoborockMessage) -> None:
|
|
@@ -88,16 +88,15 @@ class BaseV1RpcChannel(V1RpcChannel):
|
|
|
88
88
|
raise NotImplementedError
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
class
|
|
92
|
-
"""A V1 RPC channel that
|
|
91
|
+
class PickFirstAvailable(BaseV1RpcChannel):
|
|
92
|
+
"""A V1 RPC channel that tries multiple channels and picks the first that works."""
|
|
93
93
|
|
|
94
94
|
def __init__(
|
|
95
|
-
self,
|
|
95
|
+
self,
|
|
96
|
+
channel_cbs: list[Callable[[], V1RpcChannel | None]],
|
|
96
97
|
) -> None:
|
|
97
|
-
"""Initialize the
|
|
98
|
-
self.
|
|
99
|
-
self._local_rpc_channel = local_rpc_channel
|
|
100
|
-
self._mqtt_rpc_channel = mqtt_channel
|
|
98
|
+
"""Initialize the pick-first-available channel."""
|
|
99
|
+
self._channel_cbs = channel_cbs
|
|
101
100
|
|
|
102
101
|
async def _send_raw_command(
|
|
103
102
|
self,
|
|
@@ -106,9 +105,10 @@ class CombinedV1RpcChannel(BaseV1RpcChannel):
|
|
|
106
105
|
params: ParamsType = None,
|
|
107
106
|
) -> Any:
|
|
108
107
|
"""Send a command and return a parsed response RoborockBase type."""
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
for channel_cb in self._channel_cbs:
|
|
109
|
+
if channel := channel_cb():
|
|
110
|
+
return await channel.send_command(method, params=params)
|
|
111
|
+
raise RoborockException("No available connection to send command")
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
class PayloadEncodedV1RpcChannel(BaseV1RpcChannel):
|
|
@@ -132,8 +132,10 @@ class PayloadEncodedV1RpcChannel(BaseV1RpcChannel):
|
|
|
132
132
|
params: ParamsType = None,
|
|
133
133
|
) -> Any:
|
|
134
134
|
"""Send a command and return a parsed response RoborockBase type."""
|
|
135
|
-
_LOGGER.debug("Sending command (%s): %s, params=%s", self._name, method, params)
|
|
136
135
|
request_message = RequestMessage(method, params=params)
|
|
136
|
+
_LOGGER.debug(
|
|
137
|
+
"Sending command (%s, request_id=%s): %s, params=%s", self._name, request_message.request_id, method, params
|
|
138
|
+
)
|
|
137
139
|
message = self._payload_encoder(request_message)
|
|
138
140
|
|
|
139
141
|
future: asyncio.Future[dict[str, Any]] = asyncio.Future()
|
|
@@ -141,8 +143,10 @@ class PayloadEncodedV1RpcChannel(BaseV1RpcChannel):
|
|
|
141
143
|
def find_response(response_message: RoborockMessage) -> None:
|
|
142
144
|
try:
|
|
143
145
|
decoded = decode_rpc_response(response_message)
|
|
144
|
-
except RoborockException:
|
|
146
|
+
except RoborockException as ex:
|
|
147
|
+
_LOGGER.debug("Exception while decoding message (%s): %s", response_message, ex)
|
|
145
148
|
return
|
|
149
|
+
_LOGGER.debug("Received response (request_id=%s): %s", self._name, decoded.request_id)
|
|
146
150
|
if decoded.request_id == request_message.request_id:
|
|
147
151
|
future.set_result(decoded.data)
|
|
148
152
|
|
|
@@ -166,11 +170,10 @@ def create_mqtt_rpc_channel(mqtt_channel: MqttChannel, security_data: SecurityDa
|
|
|
166
170
|
)
|
|
167
171
|
|
|
168
172
|
|
|
169
|
-
def
|
|
170
|
-
"""Create a V1 RPC channel
|
|
171
|
-
|
|
173
|
+
def create_local_rpc_channel(local_channel: LocalChannel) -> V1RpcChannel:
|
|
174
|
+
"""Create a V1 RPC channel using a local channel."""
|
|
175
|
+
return PayloadEncodedV1RpcChannel(
|
|
172
176
|
"local",
|
|
173
177
|
local_channel,
|
|
174
178
|
lambda x: x.encode_message(RoborockMessageProtocol.GENERAL_REQUEST),
|
|
175
179
|
)
|
|
176
|
-
return CombinedV1RpcChannel(local_channel, local_rpc_channel, mqtt_rpc_channel)
|
|
@@ -109,7 +109,7 @@ class ResponseMessage:
|
|
|
109
109
|
def decode_rpc_response(message: RoborockMessage) -> ResponseMessage:
|
|
110
110
|
"""Decode a V1 RPC_RESPONSE message."""
|
|
111
111
|
if not message.payload:
|
|
112
|
-
|
|
112
|
+
return ResponseMessage(request_id=message.seq, data={})
|
|
113
113
|
try:
|
|
114
114
|
payload = json.loads(message.payload.decode())
|
|
115
115
|
except (json.JSONDecodeError, TypeError) as e:
|
|
@@ -141,6 +141,8 @@ def decode_rpc_response(message: RoborockMessage) -> ResponseMessage:
|
|
|
141
141
|
_LOGGER.debug("Decoded V1 message result: %s", result)
|
|
142
142
|
if isinstance(result, list) and result:
|
|
143
143
|
result = result[0]
|
|
144
|
+
if isinstance(result, str) and result == "ok":
|
|
145
|
+
result = {}
|
|
144
146
|
if not isinstance(result, dict):
|
|
145
147
|
raise RoborockException(f"Invalid V1 message format: 'result' should be a dictionary for {message.payload!r}")
|
|
146
148
|
return ResponseMessage(request_id=request_id, data=result)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.40.1 → python_roborock-2.41.1}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|