Skip to content

Commit

Permalink
feat(ui): Manage voice packs (#1108)
Browse files Browse the repository at this point in the history
  • Loading branch information
ccoors committed Sep 27, 2021
1 parent c00d174 commit 0d02c27
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 87 deletions.
1 change: 1 addition & 0 deletions backend/lib/robots/mock/MockRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class MockRobot extends ValetudoRobot {
this.registerCapability(new capabilities.MockAutoEmptyDockManualTriggerCapability({robot: this}));
this.registerCapability(new capabilities.MockAutoEmptyDockAutoEmptyControlCapability({robot: this}));
this.registerCapability(new capabilities.MockMappingPassCapability({robot: this}));
this.registerCapability(new capabilities.MockVoicePackManagementCapability({robot: this}));

// Raise events to make them visible in the UI
options.valetudoEventStore.raise(new DustBinFullValetudoEvent({}));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const ValetudoVoicePackOperationStatus = require("../../../entities/core/ValetudoVoicePackOperationStatus");
const VoicePackManagementCapability = require("../../../core/capabilities/VoicePackManagementCapability");

/**
* @extends VoicePackManagementCapability<import("../MockRobot")>
*/
class MockVoicePackManagementCapability extends VoicePackManagementCapability {
/**
* @param {object} options
* @param {import("../MockRobot")} options.robot
*/
constructor(options) {
super(options);

this.current_language = "EN";
this.status = ValetudoVoicePackOperationStatus.TYPE.IDLE;
this.progress = undefined;
}

async getCurrentVoiceLanguage() {
return this.current_language;
}

async downloadVoicePack(options) {
this.status = ValetudoVoicePackOperationStatus.TYPE.DOWNLOADING;
this.progress = 0;

// Simulate download
for (let i = 0; i < 5; i++) {
setTimeout(() => {
this.progress += 20;
}, i * 1000);
}

setTimeout(() => {
this.status = ValetudoVoicePackOperationStatus.TYPE.INSTALLING;
this.progress = 0;
}, 6000);

// Simulate installing
for (let i = 0; i < 5; i++) {
setTimeout(() => {
this.progress += 20;
}, 7000 + i * 1000);
}

setTimeout(() => {
this.status = ValetudoVoicePackOperationStatus.TYPE.IDLE;
this.current_language = options.language;
}, 13000);
}

async getVoicePackOperationStatus() {
let statusOptions = {
type: this.status,
progress: this.progress,
};

return new ValetudoVoicePackOperationStatus(statusOptions);
}
}

module.exports = MockVoicePackManagementCapability;
1 change: 1 addition & 0 deletions backend/lib/robots/mock/capabilities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
MockPersistentMapControlCapability: require("./MockPersistentMapControlCapability"),
MockSpeakerTestCapability: require("./MockSpeakerTestCapability"),
MockSpeakerVolumeControlCapability: require("./MockSpeakerVolumeControlCapability"),
MockVoicePackManagementCapability: require("./MockVoicePackManagementCapability"),
MockWaterUsageControlCapability: require("./MockWaterUsageControlCapability"),
MockWifiConfigurationCapability: require("./MockWifiConfigurationCapability"),
MockZoneCleaningCapability: require("./MockZoneCleaningCapability"),
Expand Down
46 changes: 33 additions & 13 deletions frontend/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
ValetudoEvent,
ValetudoEventInteractionContext,
ValetudoVersion,
VoicePackManagementCommand,
VoicePackManagementStatus,
Zone,
ZonePreset,
ZoneProperties,
Expand Down Expand Up @@ -132,7 +134,7 @@ export const updatePresetSelection = async (
capability: Capability.FanSpeedControl | Capability.WaterUsageControl,
level: PresetSelectionState["value"]
): Promise<void> => {
await valetudoAPI.put<void>(`/robot/capabilities/${capability}/preset`, {
await valetudoAPI.put(`/robot/capabilities/${capability}/preset`, {
name: level,
});
};
Expand All @@ -141,7 +143,7 @@ export type BasicControlCommand = "start" | "stop" | "pause" | "home";
export const sendBasicControlCommand = async (
command: BasicControlCommand
): Promise<void> => {
await valetudoAPI.put<void>(
await valetudoAPI.put(
`/robot/capabilities/${Capability.BasicControl}`,
{
action: command,
Expand All @@ -150,7 +152,7 @@ export const sendBasicControlCommand = async (
};

export const sendGoToCommand = async (point: Point): Promise<void> => {
await valetudoAPI.put<void>(
await valetudoAPI.put(
`/robot/capabilities/${Capability.GoToLocation}`,
{
action: "goto",
Expand Down Expand Up @@ -180,7 +182,7 @@ export const fetchZoneProperties = async (): Promise<ZoneProperties> => {
};

export const sendCleanZonePresetCommand = async (id: string): Promise<void> => {
await valetudoAPI.put<void>(
await valetudoAPI.put(
`/robot/capabilities/${Capability.ZoneCleaning}/presets/${id}`,
{
action: "clean",
Expand All @@ -191,7 +193,7 @@ export const sendCleanZonePresetCommand = async (id: string): Promise<void> => {
export const sendCleanTemporaryZonesCommand = async (
zones: Zone[]
): Promise<void> => {
await valetudoAPI.put<void>(
await valetudoAPI.put(
`/robot/capabilities/${Capability.ZoneCleaning}`,
{
action: "clean",
Expand Down Expand Up @@ -221,7 +223,7 @@ export const fetchMapSegmentationProperties = async (): Promise<MapSegmentationP
export const sendCleanSegmentsCommand = async (
parameters: MapSegmentationActionRequestParameters
): Promise<void> => {
await valetudoAPI.put<void>(
await valetudoAPI.put(
`/robot/capabilities/${Capability.MapSegmentation}`,
{
action: "start_segment_action",
Expand All @@ -245,7 +247,7 @@ export const fetchGoToLocationPresets = async (): Promise<Segment[]> => {
export const sendGoToLocationPresetCommand = async (
id: string
): Promise<void> => {
await valetudoAPI.put<void>(
await valetudoAPI.put(
`/robot/capabilities/${Capability.GoToLocation}/presets/${id}`,
{
action: "goto",
Expand All @@ -254,13 +256,13 @@ export const sendGoToLocationPresetCommand = async (
};

export const sendLocateCommand = async (): Promise<void> => {
await valetudoAPI.put<void>(`/robot/capabilities/${Capability.Locate}`, {
await valetudoAPI.put(`/robot/capabilities/${Capability.Locate}`, {
action: "locate",
});
};

export const sendAutoEmptyDockManualTriggerCommand = async (): Promise<void> => {
await valetudoAPI.put<void>(`/robot/capabilities/${Capability.AutoEmptyDockManualTrigger}`, {
await valetudoAPI.put(`/robot/capabilities/${Capability.AutoEmptyDockManualTrigger}`, {
action: "trigger",
});
};
Expand All @@ -279,7 +281,7 @@ export const sendConsumableReset = async (parameters: ConsumableId): Promise<voi
urlFragment += `/${parameters.subType}`;
}
return valetudoAPI
.put<MQTTConfiguration>(`/robot/capabilities/${Capability.ConsumableMonitoring}/${urlFragment}`, {
.put(`/robot/capabilities/${Capability.ConsumableMonitoring}/${urlFragment}`, {
action: "reset",
})
.then(({status}) => {
Expand Down Expand Up @@ -334,7 +336,7 @@ export const fetchValetudoLogLevel = async (): Promise<LogLevel> => {

export const sendValetudoLogLevel = async (logLevel: SetLogLevel): Promise<void> => {
await valetudoAPI
.put<void>("/valetudo/log/level", logLevel)
.put("/valetudo/log/level", logLevel)
.then(({ status }) => {
if (status !== 202) {
throw new Error("Could not set new log level");
Expand Down Expand Up @@ -385,7 +387,7 @@ export const fetchMQTTConfiguration = async (): Promise<MQTTConfiguration> => {

export const sendMQTTConfiguration = async (mqttConfiguration: MQTTConfiguration): Promise<void> => {
return valetudoAPI
.put<MQTTConfiguration>("/valetudo/config/interfaces/mqtt", mqttConfiguration)
.put("/valetudo/config/interfaces/mqtt", mqttConfiguration)
.then(({status}) => {
if (status !== 202) {
throw new Error("Could not update MQTT configuration");
Expand Down Expand Up @@ -524,8 +526,26 @@ export const sendSpeakerVolume = async (volume: number): Promise<void> => {
});
};

export const fetchVoicePackManagementState = async (): Promise<VoicePackManagementStatus> => {
return valetudoAPI
.get<VoicePackManagementStatus>(`/robot/capabilities/${Capability.VoicePackManagement}`)
.then(({ data }) => {
return data;
});
};

export const sendVoicePackManagementCommand = async (command: VoicePackManagementCommand): Promise<void> => {
return valetudoAPI
.put(`/robot/capabilities/${Capability.VoicePackManagement}`, command)
.then(({status}) => {
if (status !== 200) {
throw new Error("Could not send voice pack management command");
}
});
};

export const sendSpeakerTestCommand = async (): Promise<void> => {
await valetudoAPI.put<void>(`/robot/capabilities/${Capability.SpeakerTest}`, {
await valetudoAPI.put(`/robot/capabilities/${Capability.SpeakerTest}`, {
action: "play_test_sound",
});
};
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/api/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
fetchValetudoInformation,
fetchValetudoLog,
fetchValetudoLogLevel,
fetchVoicePackManagementState,
fetchZonePresets,
fetchZoneProperties,
sendAutoEmptyDockAutoEmptyControlEnable,
Expand All @@ -62,6 +63,7 @@ import {
sendTimerUpdate,
sendValetudoEventInteraction,
sendValetudoLogLevel,
sendVoicePackManagementCommand,
subscribeToLogMessages,
subscribeToMap,
subscribeToStateAttributes,
Expand All @@ -82,7 +84,7 @@ import {
Point,
SetLogLevel,
Timer,
ValetudoEventInteractionContext,
ValetudoEventInteractionContext, VoicePackManagementCommand,
Zone,
} from "./types";
import {MutationFunction} from "react-query/types/core/types";
Expand All @@ -104,6 +106,7 @@ enum CacheKey {
GitHubRelease = "github_release",
CarpetMode = "carpet_mode",
SpeakerVolume = "speaker_volume",
VoicePackManagement = "voice_pack",
SystemHostInfo = "system_host_info",
SystemRuntimeInfo = "system_runtime_info",
MQTTConfiguration = "mqtt_configuration",
Expand Down Expand Up @@ -664,6 +667,22 @@ export const useSpeakerTestTriggerTriggerMutation = () => {
return useMutation(sendSpeakerTestCommand, {onError});
};

export const useVoicePackManagementStateQuery = () => {
return useQuery(CacheKey.VoicePackManagement, fetchVoicePackManagementState, {
staleTime: 500,
});
};

export const useVoicePackManagementMutation = () => {
return useValetudoFetchingMutation(
useOnCommandError(Capability.VoicePackManagement),
CacheKey.VoicePackManagement,
(command: VoicePackManagementCommand) => {
return sendVoicePackManagementCommand(command).then(fetchVoicePackManagementState);
}
);
};

export const useKeyLockStateQuery = () => {
return useQuery(CacheKey.KeyLockInformation, fetchKeyLockState, {
staleTime: Infinity
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,18 @@ export interface SimpleToggleState {
export interface SpeakerVolumeState {
volume: number;
}

export interface VoicePackManagementStatus {
currentLanguage: string;
operationStatus: {
type: "idle" | "downloading" | "installing" | "error";
progress?: number;
}
}

export interface VoicePackManagementCommand {
action: "download";
url: string;
language: string;
hash: string;
}
15 changes: 6 additions & 9 deletions frontend/src/robot/capabilities/Capabilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@ import {Container, Grid} from "@material-ui/core";
import MapDataManagement from "./MapDataManagement";
import Speaker from "./Speaker";
import Switches from "./Switches";
import VoicePackManagement from "./VoicePackManagement";

const Capabilities = (): JSX.Element => {
const components = [Switches, Speaker, VoicePackManagement, MapDataManagement];

return (
<Container>
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={4}>
<Switches/>
</Grid>
<Grid item xs={4} sm={6} md={4}>
<Speaker/>
</Grid>
<Grid item xs={4} sm={6} md={4}>
<MapDataManagement/>
</Grid>
{components.map((Component, idx) => {
return <Component key={idx}/>;
})}
</Grid>
</Container>
);
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/robot/capabilities/CapabilityItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import {Card, CardContent, Divider, Grid, Stack, Typography} from "@material-ui/core";
import LoadingFade from "../../components/LoadingFade";

const CapabilityItem: React.FunctionComponent<{ children: React.ReactNode, title: string, loading?: boolean }> = ({
children,
title,
loading = false
}): JSX.Element => {
return (
<Grid item xs={12} sm={6} md={4}>
<Card>
<CardContent>
<Stack direction="row" spacing={2} alignItems="center">
<Typography variant="h6" gutterBottom>
{title}
</Typography>
<LoadingFade in={loading} size={20}/>
</Stack>
<Divider sx={{mb: 1}}/>
{children}
</CardContent>
</Card>
</Grid>
);
};

export default CapabilityItem;

0 comments on commit 0d02c27

Please sign in to comment.