Skip to content

Commit 42a77dc

Browse files
committed
add audio settings, actively try to connect to ble if it is useable
1 parent 98879eb commit 42a77dc

23 files changed

Lines changed: 283 additions & 15 deletions

custom_components/mammotion/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: MammotionConfigEntry) ->
262262

263263
addresses = entry.data.get(CONF_BLE_DEVICES, {})
264264
integration = await async_get_integration(hass, DOMAIN)
265-
mammotion = MammotionClient(ha_version=integration.version)
265+
mammotion = MammotionClient(ha_version=integration.version.split("-")[0])
266266
account = entry.data.get(CONF_ACCOUNTNAME)
267267
password = entry.data.get(CONF_PASSWORD)
268268
use_wifi = entry.data.get(CONF_USE_WIFI, True)

custom_components/mammotion/button.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ async def async_press(self) -> None:
203203
def _update_task_names(
204204
coordinator: MammotionReportUpdateCoordinator,
205205
added_tasks: set[str],
206-
task_entities_by_id: dict[str, "MammotionTaskButtonSensorEntity"],
206+
task_entities_by_id: dict[str, MammotionTaskButtonSensorEntity],
207207
) -> None:
208208
"""Rename task button entities whose plan task_name has changed."""
209209
for task_id in added_tasks:
@@ -221,7 +221,7 @@ def _update_task_names(
221221
def async_add_task_entities(
222222
coordinator: MammotionReportUpdateCoordinator,
223223
added_tasks: set[str],
224-
task_entities_by_id: dict[str, "MammotionTaskButtonSensorEntity"],
224+
task_entities_by_id: dict[str, MammotionTaskButtonSensorEntity],
225225
async_add_entities: AddEntitiesCallback,
226226
) -> None:
227227
"""Handle addition of mowing task buttons."""

custom_components/mammotion/camera.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,18 @@ async def handle_refresh_stream(call: ServiceCall) -> None:
299299
mower.reporting_coordinator.set_stream_data(stream_data)
300300
mower.reporting_coordinator.async_update_listeners()
301301

302+
async def handle_start_video(call) -> None:
303+
entity_id = call.data["entity_id"]
304+
mower: MammotionMowerData = _get_mower_by_entity_id(entity_id)
305+
if mower:
306+
await mower.reporting_coordinator.join_webrtc_channel()
307+
308+
async def handle_stop_video(call) -> None:
309+
entity_id = call.data["entity_id"]
310+
mower: MammotionMowerData = _get_mower_by_entity_id(entity_id)
311+
if mower:
312+
await mower.reporting_coordinator.leave_webrtc_channel()
313+
302314
async def handle_get_tokens(call: ServiceCall) -> ServiceResponse:
303315
entity_id = call.data["entity_id"]
304316
mower: MammotionMowerData = _get_mower_by_entity_id(entity_id)
@@ -436,6 +448,8 @@ async def handle_move_backward(call: ServiceCall) -> None:
436448
)
437449

438450
hass.services.async_register("mammotion", "refresh_stream", handle_refresh_stream)
451+
hass.services.async_register("mammotion", "start_video", handle_start_video)
452+
hass.services.async_register("mammotion", "stop_video", handle_stop_video)
439453
hass.services.async_register(
440454
"mammotion",
441455
"get_tokens",

custom_components/mammotion/config_flow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ async def async_step_wifi(
204204

205205
if account and password:
206206
integration = await async_get_integration(self.hass, DOMAIN)
207-
temp_client = MammotionClient(ha_version=integration.version)
207+
temp_client = MammotionClient(ha_version=integration.version.split("-")[0])
208208
try:
209209
session = aiohttp_client.async_get_clientsession(self.hass)
210210
await temp_client.login_and_initiate_cloud(
@@ -302,7 +302,7 @@ async def async_step_reconfigure(
302302

303303
if account and password:
304304
integration = await async_get_integration(self.hass, DOMAIN)
305-
temp_client = MammotionClient(ha_version=integration.version)
305+
temp_client = MammotionClient(ha_version=integration.version.split("-")[0])
306306
try:
307307
session = aiohttp_client.async_get_clientsession(self.hass)
308308
await temp_client.login_and_initiate_cloud(

custom_components/mammotion/coordinator.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
)
5656
from pymammotion.http.model.http import ErrorInfo, Response
5757
from pymammotion.mammotion.commands.mammotion_command import MammotionCommand
58-
from pymammotion.proto import SystemUpdateBufMsg
58+
from pymammotion.proto import MulSex, SystemUpdateBufMsg
5959
from pymammotion.state.device_state import DeviceSnapshot
6060
from pymammotion.transport.base import (
6161
AuthError,
@@ -92,7 +92,7 @@
9292

9393
MAINTENANCE_INTERVAL = timedelta(minutes=60)
9494
DEFAULT_INTERVAL = timedelta(minutes=30)
95-
REPORT_INTERVAL = timedelta(minutes=30)
95+
REPORT_INTERVAL = timedelta(minutes=5)
9696
DEVICE_VERSION_INTERVAL = timedelta(weeks=1)
9797
MAP_INTERVAL = timedelta(minutes=60)
9898
RTK_INTERVAL = timedelta(hours=5)
@@ -232,6 +232,22 @@ def get_stream_data(self) -> Response[StreamSubscriptionResponse]:
232232
"""Return stream data."""
233233
return self._stream_data
234234

235+
async def join_webrtc_channel(self) -> None:
236+
"""Start stream command."""
237+
handle = self.manager.mower(self.device_name)
238+
if handle is None:
239+
return
240+
command = self.commands.device_agora_join_channel_with_position(enter_state=1)
241+
await self.async_send_cloud_command(handle.iot_id, command)
242+
243+
async def leave_webrtc_channel(self) -> None:
244+
"""End stream command."""
245+
handle = self.manager.mower(self.device_name)
246+
if handle is None:
247+
return
248+
command = self.commands.device_agora_join_channel_with_position(enter_state=0)
249+
await self.async_send_cloud_command(handle.iot_id, command)
250+
235251
async def set_scheduled_updates(self, enabled: bool) -> None:
236252
"""Enable or disable scheduled polling updates for this device."""
237253
device = self.manager.get_device_by_name(self.device_name)
@@ -493,6 +509,22 @@ async def async_sync_schedule(self) -> None:
493509
if self.update_failures < 5:
494510
await self.async_sync_schedule()
495511

512+
async def async_fetch_audio_config(self) -> None:
513+
"""Request current audio config (volume, language, gender) from device."""
514+
await self.async_send_command("get_car_audio_cfg")
515+
516+
async def async_set_voice_volume(self, volume: float) -> None:
517+
"""Set robot voice volume (0–100)."""
518+
await self.async_send_command("set_car_volume", volume=int(volume))
519+
520+
async def async_set_voice_on_off(self, on: bool) -> None:
521+
"""Turn robot voice on (restores 50%) or off (sets volume to 0)."""
522+
await self.async_send_command("set_car_volume", volume=50 if on else 0)
523+
524+
async def async_set_voice_gender(self, sex: str) -> None:
525+
"""Set robot voice gender (MAN or WOMAN)."""
526+
await self.async_send_command("set_car_volume_sex", sex=MulSex[sex])
527+
496528
async def async_start_stop_blades(
497529
self, start_stop: bool, blade_height: int = 60
498530
) -> None:
@@ -1191,6 +1223,11 @@ async def _async_update_data(self) -> MowingDevice:
11911223
)
11921224
)
11931225

1226+
if handle := self.manager.mower(self.device_name):
1227+
if ble := handle.get_transport(TransportType.BLE):
1228+
if ble.is_usable and not ble.is_connected:
1229+
await ble.connect()
1230+
11941231
return device
11951232

11961233
async def _async_update_notification(self, res: tuple[str, Any | None]) -> None:

custom_components/mammotion/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"domain": "mammotion",
33
"name": "Mammotion",
4-
"version": "0.5.40",
4+
"version": "0.5.41",
55
"issue_tracker": "https://github.com/mikey0000/Mammotion-HA/issues",
66
"integration_type": "device",
77
"bluetooth": [
@@ -22,5 +22,5 @@
2222
"documentation": "https://github.com/mikey0000/Mammotion-HA/wiki",
2323
"loggers": ["pymammotion"],
2424
"iot_class": "local_push",
25-
"requirements": ["pymammotion==0.7.98"]
25+
"requirements": ["pymammotion==0.7.99"]
2626
}

custom_components/mammotion/number.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ class MammotionConfigNumberEntityDescription(NumberEntityDescription): # type:
6363
),
6464
)
6565

66+
AUDIO_NUMBER_ENTITIES: tuple[MammotionConfigNumberEntityDescription, ...] = (
67+
MammotionConfigNumberEntityDescription(
68+
key="voice_volume",
69+
native_min_value=0,
70+
native_max_value=100,
71+
native_step=1,
72+
mode=NumberMode.SLIDER,
73+
native_unit_of_measurement=PERCENTAGE,
74+
set_async_fn=lambda coordinator, value: coordinator.async_set_voice_volume(
75+
value
76+
),
77+
get_fn=lambda coordinator: coordinator.data.mower_state.audio.volume,
78+
),
79+
)
80+
6681
NUMBER_ENTITIES: tuple[MammotionConfigNumberEntityDescription, ...] = (
6782
MammotionConfigNumberEntityDescription(
6883
key="start_progress",
@@ -181,6 +196,14 @@ async def async_setup_entry(
181196
)
182197
)
183198

199+
if DeviceType.is_luba_pro(mower.device.device_name):
200+
for entity_description in AUDIO_NUMBER_ENTITIES:
201+
entities.append(
202+
MammotionConfigNumberEntity(
203+
mower.reporting_coordinator, entity_description
204+
)
205+
)
206+
184207
for entity_description in MAP_OFFSET_ENTITIES:
185208
entities.append(
186209
MammotionConfigNumberEntity(

custom_components/mammotion/select.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ class MammotionAsyncConfigSelectEntityDescription(MammotionBaseEntity, SelectEnt
4444
set_fn: Callable[[MammotionBaseUpdateCoordinator, str], Awaitable[None]]
4545

4646

47+
AUDIO_SELECT_ENTITIES: tuple[MammotionAsyncConfigSelectEntityDescription, ...] = (
48+
MammotionAsyncConfigSelectEntityDescription(
49+
key="voice_gender",
50+
options=["MAN", "WOMAN"],
51+
get_fn=lambda coordinator: coordinator.data.mower_state.audio.sex,
52+
set_fn=lambda coordinator, value: coordinator.async_set_voice_gender(value),
53+
),
54+
)
55+
4756
ASYNC_SELECT_ENTITIES: tuple[MammotionAsyncConfigSelectEntityDescription, ...] = (
4857
MammotionAsyncConfigSelectEntityDescription(
4958
key="traversal_mode",
@@ -154,6 +163,14 @@ async def async_setup_entry(
154163
)
155164
)
156165

166+
if DeviceType.is_luba_pro(mower.device.device_name):
167+
for entity_description in AUDIO_SELECT_ENTITIES:
168+
entities.append(
169+
MammotionAsyncConfigSelectEntity(
170+
mower.reporting_coordinator, entity_description
171+
)
172+
)
173+
157174
for entity_description in ASYNC_SELECT_ENTITIES:
158175
entities.append(
159176
MammotionAsyncConfigSelectEntity(
@@ -163,13 +180,19 @@ async def async_setup_entry(
163180

164181
bypass_mode_desc = MammotionConfigSelectEntityDescription(
165182
key="bypass_mode",
166-
options=[s.name for s in DetectionStrategy.for_device(mower.device.device_name)],
183+
options=[
184+
s.name for s in DetectionStrategy.for_device(mower.device.device_name)
185+
],
167186
set_fn=lambda coordinator, value: setattr(
168-
coordinator.operation_settings, "ultra_wave", DetectionStrategy[value].value
187+
coordinator.operation_settings,
188+
"ultra_wave",
189+
DetectionStrategy[value].value,
169190
),
170191
async_set_fn=lambda coordinator: coordinator.async_modify_plan_if_mowing(),
171192
)
172-
entities.append(MammotionConfigSelectEntity(mower.reporting_coordinator, bypass_mode_desc))
193+
entities.append(
194+
MammotionConfigSelectEntity(mower.reporting_coordinator, bypass_mode_desc)
195+
)
173196

174197
if DeviceType.is_luba1(mower.device.device_name):
175198
for entity_description in LUBA1_SELECT_ENTITIES:

custom_components/mammotion/strings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@
231231
},
232232
"schedule_updates": {
233233
"name": "Turn updates on/off"
234+
},
235+
"voice_on_off": {
236+
"name": "Voice on/off"
234237
}
235238
},
236239
"select": {
@@ -287,9 +290,19 @@
287290
"absolute_angle": "North",
288291
"random_angle": "Random"
289292
}
293+
},
294+
"voice_gender": {
295+
"name": "Voice gender",
296+
"state": {
297+
"MAN": "Male",
298+
"WOMAN": "Female"
299+
}
290300
}
291301
},
292302
"number": {
303+
"voice_volume": {
304+
"name": "Voice volume"
305+
},
293306
"start_progress": {
294307
"name": "Start progress"
295308
},

custom_components/mammotion/switch.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ class MammotionConfigAreaSwitchEntityDescription(MammotionSwitchEntityDescriptio
9494
),
9595
)
9696

97+
AUDIO_SWITCH_ENTITIES: tuple[MammotionAsyncSwitchEntityDescription, ...] = (
98+
MammotionAsyncSwitchEntityDescription(
99+
key="voice_on_off",
100+
polling=True,
101+
poll_func=lambda coordinator: coordinator.async_fetch_audio_config(),
102+
is_on_func=lambda coordinator: coordinator.data.mower_state.audio.volume > 0,
103+
set_fn=lambda coordinator, value: coordinator.async_set_voice_on_off(value),
104+
entity_category=EntityCategory.CONFIG,
105+
),
106+
)
107+
97108
SWITCH_ENTITIES: tuple[MammotionAsyncSwitchEntityDescription, ...] = (
98109
MammotionAsyncSwitchEntityDescription(
99110
key="side_led",
@@ -170,6 +181,10 @@ async def async_setup_entry(
170181
entity = MammotionSwitchEntity(coordinator, entity_description)
171182
entities.append(entity)
172183

184+
if DeviceType.is_luba_pro(mower.device.device_name):
185+
for entity_description in AUDIO_SWITCH_ENTITIES:
186+
entities.append(MammotionSwitchEntity(coordinator, entity_description))
187+
173188
for entity_description in CONFIG_SWITCH_ENTITIES:
174189
config_entity = MammotionConfigSwitchEntity(coordinator, entity_description)
175190
entities.append(config_entity)

0 commit comments

Comments
 (0)