]> git.basschouten.com Git - openhab-addons.git/blob
4764854ad24b9e6181fc4f7f71a3480d8df10c64
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mycroft.internal.channels;
14
15 import java.util.Arrays;
16 import java.util.List;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.openhab.binding.mycroft.internal.MycroftBindingConstants;
20 import org.openhab.binding.mycroft.internal.MycroftHandler;
21 import org.openhab.binding.mycroft.internal.api.MessageType;
22 import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
23 import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeDecrease;
24 import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeGet;
25 import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeGetResponse;
26 import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeIncrease;
27 import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeSet;
28 import org.openhab.core.library.types.IncreaseDecreaseType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.PercentType;
31 import org.openhab.core.types.Command;
32 import org.openhab.core.types.RefreshType;
33 import org.openhab.core.types.State;
34
35 /**
36  * The channel responsible for handling the volume of the Mycroft speaker
37  * QUITE FUNCTIONAL but with workaround
38  * (see https://community.mycroft.ai/t/openhab-plugin-development-audio-volume-message-types-missing/10576
39  * and https://github.com/MycroftAI/skill-volume/issues/53)
40  *
41  * @author Gwendal Roulleau - Initial contribution
42  */
43 @NonNullByDefault
44 public class VolumeChannel extends MycroftChannel<State> {
45
46     /**
47      * As the MessageVolumeSet is, contrary to the documentation, not listened to by Mycroft,
48      * we use a workaround and send a message simulating an intent detection
49      */
50     public static final String VOLUME_SETTER_MESSAGE = "{\"type\": \"mycroft-volume.mycroftai:SetVolume\", \"data\": {\"intent_type\": \"mycroft-volume.mycroftai:SetVolume\", \"mycroft_volume_mycroftaiVolume\": \"volume\", \"mycroft_volume_mycroftaiLevel\": \"$$VOLUME\", \"mycroft_volume_mycroftaiTo\": \"to\", \"target\": null, \"confidence\": 0.6000000000000001, \"__tags__\": [{\"match\": \"volume\", \"key\": \"volume\", \"start_token\": 1, \"entities\": [{\"key\": \"volume\", \"match\": \"volume\", \"data\": [[\"volume\", \"mycroft_volume_mycroftaiVolume\"]], \"confidence\": 1.0}], \"end_token\": 1, \"from_context\": false}, {\"match\": \"$$VOLUME\", \"key\": \"$$VOLUME\", \"start_token\": 3, \"entities\": [{\"key\": \"$$VOLUME\", \"match\": \"$$VOLUME\", \"data\": [[\"$$VOLUME\", \"mycroft_volume_mycroftaiLevel\"]], \"confidence\": 1.0}], \"end_token\": 3, \"from_context\": false}, {\"match\": \"to\", \"key\": \"to\", \"start_token\": 2, \"entities\": [{\"key\": \"to\", \"match\": \"to\", \"data\": [[\"to\", \"mycroft_volume_mycroftaiTo\"]], \"confidence\": 1.0}], \"end_token\": 2, \"from_context\": false}], \"utterance\": \"set volume to $$VOLUME\", \"utterances\": [\"set volume to X\"]}, \"context\": {\"client_name\": \"mycroft_cli\", \"source\": [\"skills\"], \"destination\": \"debug_cli\"}}";
51
52     private PercentType lastVolume = new PercentType(50);
53     private PercentType lastNonZeroVolume = new PercentType(50);
54
55     public VolumeChannel(MycroftHandler handler) {
56         super(handler, MycroftBindingConstants.VOLUME_CHANNEL);
57     }
58
59     @Override
60     public List<MessageType> getMessageToListenTo() {
61         // we don't listen to mute/unmute message because duck/unduck seems sufficient
62         // and we don't want to change state twice for the same event
63         // but it should be tested on mark I, as volume is handled differently
64         return Arrays.asList(MessageType.mycroft_volume_get_response, MessageType.mycroft_volume_set,
65                 MessageType.mycroft_volume_increase, MessageType.mycroft_volume_decrease,
66                 MessageType.mycroft_volume_duck, MessageType.mycroft_volume_unduck);
67     }
68
69     @Override
70     public void messageReceived(BaseMessage message) {
71
72         if (message.type == MessageType.mycroft_volume_get_response) {
73             float volumeGet = ((MessageVolumeGetResponse) message).data.percent;
74             updateAndSaveMyState(normalizeVolume(volumeGet));
75         } else if (message.type == MessageType.mycroft_volume_set) {
76             float volumeSet = ((MessageVolumeSet) message).data.percent;
77             updateAndSaveMyState(normalizeVolume(volumeSet));
78         } else if (message.type == MessageType.mycroft_volume_duck) {
79             updateAndSaveMyState(new PercentType(0));
80         } else if (message.type == MessageType.mycroft_volume_unduck) {
81             updateAndSaveMyState(lastNonZeroVolume);
82         } else if (message.type == MessageType.mycroft_volume_increase) {
83             updateAndSaveMyState(normalizeVolume(lastVolume.intValue() + 10));
84         } else if (message.type == MessageType.mycroft_volume_decrease) {
85             updateAndSaveMyState(normalizeVolume(lastVolume.intValue() - 10));
86         }
87     }
88
89     protected final void updateAndSaveMyState(State state) {
90         if (state instanceof PercentType) {
91             this.lastVolume = ((PercentType) state);
92             if (((PercentType) state).intValue() > 0) {
93                 this.lastNonZeroVolume = ((PercentType) state);
94             }
95         }
96         super.updateMyState(state);
97     }
98
99     /**
100      * Protection method for volume with
101      * potentially wrong value.
102      *
103      * @param volume The requested volume, on a scale from 0 to 100.
104      *            Could be out of bond, then it will be corrected.
105      * @return A safe volume in PercentType between 0 and 100
106      */
107     private PercentType normalizeVolume(int volume) {
108         if (volume >= 100) {
109             return PercentType.HUNDRED;
110         } else if (volume <= 0) {
111             return PercentType.ZERO;
112         } else {
113             return new PercentType(volume);
114         }
115     }
116
117     /**
118      * Protection method for volume with
119      * potentially wrong value.
120      *
121      * @param volume The requested volume, on a scale from 0 to 1.
122      * @return A safe volume in PercentType between 0 and 100
123      */
124     private PercentType normalizeVolume(float volume) {
125         if (volume >= 1) {
126             return PercentType.HUNDRED;
127         } else if (volume <= 0) {
128             return PercentType.ZERO;
129         } else {
130             return new PercentType(Math.round(volume * 100));
131         }
132     }
133
134     public float toMycroftVolume(PercentType percentType) {
135         return Float.valueOf(percentType.intValue());
136     }
137
138     public PercentType computeNewVolume(int valueAdded) {
139         return new PercentType(lastVolume.intValue() + valueAdded);
140     }
141
142     private boolean sendSetMessage(float volume) {
143         String messageToSend = VOLUME_SETTER_MESSAGE.replaceAll("\\$\\$VOLUME", Float.valueOf(volume).toString());
144         return handler.sendMessage(messageToSend);
145     }
146
147     @Override
148     public void handleCommand(Command command) {
149         if (command instanceof OnOffType) {
150             if (command == OnOffType.ON) {
151                 if (sendSetMessage(toMycroftVolume(lastNonZeroVolume))) {
152                     updateAndSaveMyState(lastNonZeroVolume);
153                 }
154             }
155             if (command == OnOffType.OFF) {
156                 if (sendSetMessage(0)) {
157                     updateAndSaveMyState(PercentType.ZERO);
158                 }
159             }
160         } else if (command instanceof IncreaseDecreaseType) {
161             if (command == IncreaseDecreaseType.INCREASE) {
162                 if (handler.sendMessage(new MessageVolumeIncrease())) {
163                     updateAndSaveMyState(computeNewVolume(10));
164                 }
165             }
166             if (command == IncreaseDecreaseType.DECREASE) {
167                 handler.sendMessage(new MessageVolumeDecrease());
168                 updateAndSaveMyState(computeNewVolume(-10));
169             }
170         } else if (command instanceof PercentType) {
171             sendSetMessage(toMycroftVolume((PercentType) command));
172             updateAndSaveMyState((PercentType) command);
173         } else if (command instanceof RefreshType) {
174             handler.sendMessage(new MessageVolumeGet());
175         }
176     }
177 }