mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[mycroft] Initial contribution (#11040)
This binding will connect to Mycroft A.I. in order to control it or react to event by listening on the message bus. Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com>
This commit is contained in:
parent
ab1ce7f6db
commit
40b5932817
@ -198,6 +198,7 @@
|
||||
/bundles/org.openhab.binding.mqtt.homeassistant/ @davidgraeff @antroids
|
||||
/bundles/org.openhab.binding.mqtt.homie/ @davidgraeff
|
||||
/bundles/org.openhab.binding.myq/ @digitaldan
|
||||
/bundles/org.openhab.binding.mycroft/ @dalgwen
|
||||
/bundles/org.openhab.binding.mystrom/ @pail23
|
||||
/bundles/org.openhab.binding.nanoleaf/ @raepple @stefan-hoehn
|
||||
/bundles/org.openhab.binding.neato/ @jjlauterbach
|
||||
|
@ -976,6 +976,11 @@
|
||||
<artifactId>org.openhab.binding.mqtt.homie</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.mycroft</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.myq</artifactId>
|
||||
|
13
bundles/org.openhab.binding.mycroft/NOTICE
Normal file
13
bundles/org.openhab.binding.mycroft/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
128
bundles/org.openhab.binding.mycroft/README.md
Normal file
128
bundles/org.openhab.binding.mycroft/README.md
Normal file
@ -0,0 +1,128 @@
|
||||
# Mycroft Binding
|
||||
|
||||
This binding connects to Mycroft A.I. in order to control it or react to events by listening on the message bus.
|
||||
|
||||
Possibilies include:
|
||||
|
||||
- Press a button in openHAB to wake Mycroft without using a wake word.
|
||||
- Simulate a voice command to launch a skill, as if you just spoke it
|
||||
- Send some text that Mycroft will say (Using its Text To Speech service)
|
||||
- Control the music player
|
||||
- Mute the sound volume of Mycroft
|
||||
- React to all the aforementioned events ...
|
||||
- ... and send/receive any other kind of messages on the message bus
|
||||
|
||||
|
||||
## Supported Things
|
||||
|
||||
The only thing managed by this binding is a Mycroft instance
|
||||
|
||||
| Thing Type ID | Description |
|
||||
|--------------------|----------------------------------------------------------------------------|
|
||||
| mycroft | A Mark I/II, a Picroft, or any other variant exposing the message bus |
|
||||
|
||||
|
||||
|
||||
## Discovery
|
||||
|
||||
There is no discovery service, as Mycroft doesn't announce itself on the network.
|
||||
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The configuration is simple, as you just need to give the IP/hostname of the Mycroft instance accessible on the network.
|
||||
The default port is 8181, which can be changed.
|
||||
|
||||
```
|
||||
Thing mycroft:mycroft:myMycroft "Mycroft A.I." @ "Living Room" [host="192.168.X.X"]
|
||||
```
|
||||
|
||||
| property | type | description | mandatory |
|
||||
|--------------------------|------------------------|------------------------------------------------------------------|-----------|
|
||||
| host | IP or string | IP address or hostname | Yes |
|
||||
| port | integer | Port to reach Mycroft (default 8181) | No |
|
||||
| volume_restoration_level | integer | When unmuted, force Mycroft to restore volume to this value | No |
|
||||
|
||||
|
||||
## Channels
|
||||
|
||||
A Mycroft thing has the following channels:
|
||||
|
||||
|
||||
| channel type id | Item type | description |
|
||||
|------------------------------|-----------|------------------------------------------------------------------------------------------------|
|
||||
| listen | Switch | Switch to ON when Mycroft is listening. Can simulate a wake word detection to trigger the STT |
|
||||
| speak | String | The last sentence Mycroft speaks |
|
||||
| utterance | String | The last utterance Mycroft receive |
|
||||
| player | Player | The music player Mycroft is currently controlling |
|
||||
| volume_mute | Switch | Mute the Mycroft speaker |
|
||||
| volume | Dimmer | The volume of the Mycroft speaker. (Note : Value unreliable until a volume change occured) |
|
||||
| full_message | String | The last message (full json) seen on the Mycroft Bus. Filtered by the messageTypes properties |
|
||||
|
||||
|
||||
The channel 'full_message' has the following configuration available:
|
||||
|
||||
| property | type | description | mandatory |
|
||||
|---------------|---------------------------------|-------------------------------------------------------------------------|-----------|
|
||||
| messageTypes | List of string, comma separated | Only these message types will be forwarded to the Full Message Channel | No |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
A manual setup through a `things/mycroft.things` file could look like this:
|
||||
|
||||
```java
|
||||
Thing mycroft:mycroft:myMycroft "Mycroft A.I." @ "Living Room" [host="192.168.X.X", port=8181] {
|
||||
Channels:
|
||||
Type full-message-channel : Text [
|
||||
messageTypes="message.type.1,message.type.4"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Item Configuration
|
||||
|
||||
The `mycroft.item` file:
|
||||
|
||||
```java
|
||||
Switch myMycroft_mute "Mute" { channel="mycroft:mycroft:myMycroft:volume_mute" }
|
||||
Dimmer myMycroft_volume "Volume [%d]" { channel="mycroft:mycroft:myMycroft:volume" }
|
||||
Player myMycroft_player "Control" { channel="mycroft:mycroft:myMycroft:player" }
|
||||
Switch myMycroft_listen "Wake and listen" { channel="mycroft:mycroft:myMycroft:listen" }
|
||||
String myMycroft_speak "Speak STT" { channel="mycroft:mycroft:myMycroft:speak" }
|
||||
String myMycroft_utterance "Utterance" { channel="mycroft:mycroft:myMycroft:utterance" }
|
||||
String myMycroft_fullmessage "Full JSON message" { channel="mycroft:mycroft:myMycroft:full_message" }
|
||||
```
|
||||
|
||||
### Sitemap Configuration
|
||||
|
||||
A `demo.sitemap` file:
|
||||
|
||||
```
|
||||
sitemap demo label="myMycroft"
|
||||
{
|
||||
Frame label="myMycroft" {
|
||||
Switch item=myMycroft_mute
|
||||
Slider item=myMycroft_volume
|
||||
Default item=myMycroft_player
|
||||
Switch item=myMycroft_listen
|
||||
Text item=myMycroft_speak
|
||||
Text item=myMycroft_utterance
|
||||
Text item=myMycroft_fullmessage
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Ask Mycroft to say something
|
||||
|
||||
mycroft.rules
|
||||
|
||||
```java
|
||||
rule "Say Hello"
|
||||
when
|
||||
Item Presence_Isaac changed
|
||||
then
|
||||
myMycroft_speak.sendCommand("Hello Isaac")
|
||||
end
|
||||
```
|
17
bundles/org.openhab.binding.mycroft/pom.xml
Normal file
17
bundles/org.openhab.binding.mycroft/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.mycroft</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Mycroft Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.mycroft-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-mycroft" description="mycroft Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-http</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mycroft/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link MycroftBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MycroftBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "mycroft";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID MYCROFT = new ThingTypeUID(BINDING_ID, "mycroft");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String LISTEN_CHANNEL = "listen";
|
||||
public static final String SPEAK_CHANNEL = "speak";
|
||||
public static final String PLAYER_CHANNEL = "player";
|
||||
public static final String VOLUME_CHANNEL = "volume";
|
||||
public static final String VOLUME_MUTE_CHANNEL = "volume_mute";
|
||||
public static final String UTTERANCE_CHANNEL = "utterance";
|
||||
public static final String FULL_MESSAGE_CHANNEL = "full_message";
|
||||
|
||||
// Channel property :
|
||||
public static final String FULL_MESSAGE_CHANNEL_MESSAGE_TYPE_PROPERTY = "messageTypes";
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link MycroftConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MycroftConfiguration {
|
||||
|
||||
public String host = "";
|
||||
public int port = 8181;
|
||||
public int volume_restoration_level = 0;
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
import org.openhab.binding.mycroft.internal.api.MycroftConnection;
|
||||
import org.openhab.binding.mycroft.internal.api.MycroftConnectionListener;
|
||||
import org.openhab.binding.mycroft.internal.api.MycroftMessageListener;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeGet;
|
||||
import org.openhab.binding.mycroft.internal.channels.AudioPlayerChannel;
|
||||
import org.openhab.binding.mycroft.internal.channels.ChannelCommandHandler;
|
||||
import org.openhab.binding.mycroft.internal.channels.FullMessageChannel;
|
||||
import org.openhab.binding.mycroft.internal.channels.ListenChannel;
|
||||
import org.openhab.binding.mycroft.internal.channels.MuteChannel;
|
||||
import org.openhab.binding.mycroft.internal.channels.MycroftChannel;
|
||||
import org.openhab.binding.mycroft.internal.channels.SpeakChannel;
|
||||
import org.openhab.binding.mycroft.internal.channels.UtteranceChannel;
|
||||
import org.openhab.binding.mycroft.internal.channels.VolumeChannel;
|
||||
import org.openhab.core.io.net.http.WebSocketFactory;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MycroftHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MycroftHandler extends BaseThingHandler implements MycroftConnectionListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MycroftHandler.class);
|
||||
|
||||
private final WebSocketFactory webSocketFactory;
|
||||
private @NonNullByDefault({}) MycroftConnection connection;
|
||||
private @Nullable ScheduledFuture<?> scheduledFuture;
|
||||
private MycroftConfiguration config = new MycroftConfiguration();
|
||||
private boolean thingDisposing = false;
|
||||
protected Map<ChannelUID, MycroftChannel<?>> mycroftChannels = new HashMap<>();
|
||||
|
||||
/** The reconnect frequency in case of error */
|
||||
private static final int POLL_FREQUENCY_SEC = 30;
|
||||
private int sometimesSendVolumeRequest = 0;
|
||||
|
||||
public MycroftHandler(Thing thing, WebSocketFactory webSocketFactory) {
|
||||
super(thing);
|
||||
this.webSocketFactory = webSocketFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the API request or websocket reconnect timer
|
||||
*/
|
||||
private void stopTimer() {
|
||||
ScheduledFuture<?> future = scheduledFuture;
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
scheduledFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the websocket connection.
|
||||
* It sometimes also sends a get volume request to check the connection and refresh the volume.
|
||||
*/
|
||||
private void checkOrstartWebsocket() {
|
||||
if (thingDisposing) {
|
||||
return;
|
||||
}
|
||||
if (connection.isConnected()) {
|
||||
// sometimes test the connection by sending a real message
|
||||
// AND refreshing volume in the same step
|
||||
if (sometimesSendVolumeRequest >= 3) { // arbitrary one on three times
|
||||
sometimesSendVolumeRequest = 0;
|
||||
sendMessage(new MessageVolumeGet());
|
||||
} else {
|
||||
sometimesSendVolumeRequest++;
|
||||
}
|
||||
} else {
|
||||
connection.start(config.host, config.port);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
ChannelCommandHandler channelCommand = mycroftChannels.get(channelUID);
|
||||
if (channelCommand == null) {
|
||||
logger.error("Command {} for channel {} cannot be handled", command.toString(), channelUID.toString());
|
||||
} else {
|
||||
channelCommand.handleCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
thingDisposing = false;
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
logger.debug("Start initializing Mycroft {}", thing.getUID());
|
||||
|
||||
String websocketID = thing.getUID().getAsString().replace(':', '-');
|
||||
if (websocketID.length() < 4) {
|
||||
websocketID = "mycroft-" + websocketID;
|
||||
}
|
||||
if (websocketID.length() > 20) {
|
||||
websocketID = websocketID.substring(websocketID.length() - 20);
|
||||
}
|
||||
this.connection = new MycroftConnection(this, webSocketFactory.createWebSocketClient(websocketID));
|
||||
|
||||
config = getConfigAs(MycroftConfiguration.class);
|
||||
if (config.host.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "No host defined");
|
||||
return;
|
||||
} else if (config.port < 0 || config.port > 0xFFFF) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Port should be between 0 and 65536");
|
||||
return;
|
||||
}
|
||||
scheduledFuture = scheduler.scheduleWithFixedDelay(this::checkOrstartWebsocket, 0, POLL_FREQUENCY_SEC,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
registerChannel(new ListenChannel(this));
|
||||
registerChannel(new VolumeChannel(this));
|
||||
registerChannel(new MuteChannel(this, config.volume_restoration_level));
|
||||
registerChannel(new SpeakChannel(this));
|
||||
registerChannel(new AudioPlayerChannel(this));
|
||||
registerChannel(new UtteranceChannel(this));
|
||||
|
||||
final Channel fullMessageChannel = getThing().getChannel(MycroftBindingConstants.FULL_MESSAGE_CHANNEL);
|
||||
@SuppressWarnings("null") // cannot be null
|
||||
String messageTypesProperty = (String) fullMessageChannel.getConfiguration()
|
||||
.get(MycroftBindingConstants.FULL_MESSAGE_CHANNEL_MESSAGE_TYPE_PROPERTY);
|
||||
|
||||
registerChannel(new FullMessageChannel(this, messageTypesProperty));
|
||||
|
||||
checkLinkedChannelsAndRegisterMessageListeners();
|
||||
}
|
||||
|
||||
private void checkLinkedChannelsAndRegisterMessageListeners() {
|
||||
for (Entry<ChannelUID, MycroftChannel<?>> channelEntry : mycroftChannels.entrySet()) {
|
||||
ChannelUID uid = channelEntry.getKey();
|
||||
MycroftChannel<?> channel = channelEntry.getValue();
|
||||
if (isLinked(uid)) {
|
||||
channel.registerListeners();
|
||||
} else {
|
||||
channel.unregisterListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
checkLinkedChannelsAndRegisterMessageListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelUnlinked(ChannelUID channelUID) {
|
||||
checkLinkedChannelsAndRegisterMessageListeners();
|
||||
}
|
||||
|
||||
private void registerChannel(MycroftChannel<?> channel) {
|
||||
mycroftChannels.put(channel.getChannelUID(), channel);
|
||||
}
|
||||
|
||||
public void registerMessageListener(MessageType messageType, MycroftMessageListener<?> listener) {
|
||||
this.connection.registerListener(messageType, listener);
|
||||
}
|
||||
|
||||
public void unregisterMessageListener(MessageType messageType, MycroftMessageListener<?> listener) {
|
||||
this.connection.unregisterListener(messageType, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionEstablished() {
|
||||
logger.debug("Mycroft thing {} is online", thing.getUID());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionLost(String reason) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
thingDisposing = true;
|
||||
stopTimer();
|
||||
connection.close();
|
||||
}
|
||||
|
||||
public <T extends State> void updateMyChannel(MycroftChannel<T> mycroftChannel, T state) {
|
||||
updateState(mycroftChannel.getChannelUID(), state);
|
||||
}
|
||||
|
||||
public boolean sendMessage(BaseMessage message) {
|
||||
try {
|
||||
connection.sendMessage(message);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.debug("Cannot send message of type {}, for reason {}", message.getClass().getName(), e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean sendMessage(String message) {
|
||||
try {
|
||||
connection.sendMessage(message);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.debug("Cannot send message of type {}, for reason {}", message.getClass().getName(), e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.net.http.WebSocketFactory;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link MycroftHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.mycroft", service = ThingHandlerFactory.class)
|
||||
public class MycroftHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(MycroftBindingConstants.MYCROFT);
|
||||
|
||||
private final WebSocketFactory webSocketFactory;
|
||||
|
||||
@Activate
|
||||
public MycroftHandlerFactory(final @Reference WebSocketFactory webSocketFactory) {
|
||||
this.webSocketFactory = webSocketFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (MycroftBindingConstants.MYCROFT.equals(thingTypeUID)) {
|
||||
return new MycroftHandler(thing, webSocketFactory);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioNext;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioPause;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioPlay;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioPrev;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioResume;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioStop;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioTrackInfo;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioTrackInfoReply;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageMicListen;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageRecognizerLoopRecordBegin;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageRecognizerLoopRecordEnd;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageRecognizerLoopUtterance;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageSpeak;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeDecrease;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeDuck;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeGet;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeGetResponse;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeIncrease;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeMute;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeSet;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeUnduck;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeUnmute;
|
||||
|
||||
/**
|
||||
* All message type of interest, issued by Mycroft, are referenced here
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum MessageType {
|
||||
|
||||
any("special-anymessages", BaseMessage.class),
|
||||
speak("speak", MessageSpeak.class),
|
||||
recognizer_loop__record_begin("recognizer_loop:record_begin", MessageRecognizerLoopRecordBegin.class),
|
||||
recognizer_loop__record_end("recognizer_loop:record_end", MessageRecognizerLoopRecordEnd.class),
|
||||
recognizer_loop__utterance("recognizer_loop:utterance", MessageRecognizerLoopUtterance.class),
|
||||
mycroft_mic_listen("mycroft.mic.listen", MessageMicListen.class),
|
||||
|
||||
mycroft_audio_service_pause("mycroft.audio.service.pause", MessageAudioPause.class),
|
||||
mycroft_audio_service_resume("mycroft.audio.service.resume", MessageAudioResume.class),
|
||||
mycroft_audio_service_stop("mycroft.audio.service.stop", MessageAudioStop.class),
|
||||
mycroft_audio_service_play("mycroft.audio.service.play", MessageAudioPlay.class),
|
||||
mycroft_audio_service_next("mycroft.audio.service.next", MessageAudioNext.class),
|
||||
mycroft_audio_service_prev("mycroft.audio.service.prev", MessageAudioPrev.class),
|
||||
mycroft_audio_service_track_info("mycroft.audio.service.track_info", MessageAudioTrackInfo.class),
|
||||
mycroft_audio_service_track_info_reply("mycroft.audio.service.track_info_reply", MessageAudioTrackInfoReply.class),
|
||||
|
||||
mycroft_volume_set("mycroft.volume.set", MessageVolumeSet.class),
|
||||
mycroft_volume_increase("mycroft.volume.increase", MessageVolumeIncrease.class),
|
||||
mycroft_volume_decrease("mycroft.volume.decrease", MessageVolumeDecrease.class),
|
||||
mycroft_volume_get("mycroft.volume.get", MessageVolumeGet.class),
|
||||
mycroft_volume_get_response("mycroft.volume.get.response", MessageVolumeGetResponse.class),
|
||||
mycroft_volume_mute("mycroft.volume.mute", MessageVolumeMute.class),
|
||||
mycroft_volume_unmute("mycroft.volume.unmute", MessageVolumeUnmute.class),
|
||||
mycroft_volume_duck("mycroft.volume.duck", MessageVolumeDuck.class),
|
||||
mycroft_volume_unduck("mycroft.volume.unduck", MessageVolumeUnduck.class),
|
||||
|
||||
mycroft_reminder_mycroftai__reminder("mycroft-reminder.mycroftai:reminder", BaseMessage.class),
|
||||
mycroft_date_time_mycroftai__timeskillupdate_display("mycroft-date-time.mycroftai:TimeSkillupdate_display",
|
||||
BaseMessage.class),
|
||||
mycroft_configuration_mycroftai__configurationskillupdate_remote(
|
||||
"mycroft-configuration.mycroftai:ConfigurationSkillupdate_remote", BaseMessage.class);
|
||||
|
||||
private @NotNull Class<? extends BaseMessage> messageTypeClass;
|
||||
private @NotNull String messageTypeName;
|
||||
|
||||
MessageType(String messageTypeName, Class<? extends BaseMessage> messageType) {
|
||||
this.messageTypeClass = messageType;
|
||||
this.messageTypeName = messageTypeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expected message type for this message
|
||||
*
|
||||
* @return The message type class associated with this type
|
||||
*/
|
||||
public @NotNull Class<? extends BaseMessage> getMessageTypeClass() {
|
||||
return messageTypeClass;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MessageType fromString(String asString) {
|
||||
return Stream.of(values()).filter(messageType -> messageType.messageTypeName.equals(asString)).findFirst()
|
||||
.orElse(any);
|
||||
}
|
||||
|
||||
public String getMessageTypeName() {
|
||||
return messageTypeName;
|
||||
}
|
||||
|
||||
protected void setMessageTypeName(String messageTypeName) {
|
||||
this.messageTypeName = messageTypeName;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
/**
|
||||
* Custom deserializer
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageTypeConverter implements JsonDeserializer<MessageType>, JsonSerializer<MessageType> {
|
||||
@Override
|
||||
public @Nullable MessageType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||
throws JsonParseException {
|
||||
MessageType messageType = MessageType.fromString(json.getAsString());
|
||||
// for message of type non recognized :
|
||||
messageType.setMessageTypeName(json.getAsString());
|
||||
return messageType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(MessageType src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.getMessageTypeName());
|
||||
}
|
||||
}
|
@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* Establishes and keeps a websocket connection to the Mycroft bus
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution. Inspired by the deconz binding.
|
||||
*/
|
||||
@WebSocket
|
||||
@NonNullByDefault
|
||||
public class MycroftConnection {
|
||||
private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger();
|
||||
private final Logger logger = LoggerFactory.getLogger(MycroftConnection.class);
|
||||
|
||||
private final WebSocketClient client;
|
||||
private final String socketName;
|
||||
private final Gson gson;
|
||||
|
||||
private final MycroftConnectionListener connectionListener;
|
||||
private final Map<MessageType, Set<MycroftMessageListener<? extends BaseMessage>>> listeners = new ConcurrentHashMap<>();
|
||||
|
||||
private ConnectionState connectionState = ConnectionState.DISCONNECTED;
|
||||
private @Nullable Session session;
|
||||
|
||||
private static final int TIMEOUT_MILLISECONDS = 3000;
|
||||
|
||||
public MycroftConnection(MycroftConnectionListener listener, WebSocketClient client) {
|
||||
this.connectionListener = listener;
|
||||
this.client = client;
|
||||
this.client.setConnectTimeout(TIMEOUT_MILLISECONDS);
|
||||
this.client.setMaxIdleTimeout(0);
|
||||
this.socketName = "Websocket-Mycroft$" + System.currentTimeMillis() + "-" + INSTANCE_COUNTER.incrementAndGet();
|
||||
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(MessageType.class, new MessageTypeConverter());
|
||||
gson = gsonBuilder.create();
|
||||
}
|
||||
|
||||
public MycroftConnection(MycroftConnectionListener listener) {
|
||||
this(listener, new WebSocketClient());
|
||||
}
|
||||
|
||||
public void start(String ip, int port) {
|
||||
if (connectionState == ConnectionState.CONNECTED) {
|
||||
return;
|
||||
} else if (connectionState == ConnectionState.CONNECTING) {
|
||||
logger.debug("{} already connecting", socketName);
|
||||
return;
|
||||
} else if (connectionState == ConnectionState.DISCONNECTING) {
|
||||
logger.warn("{} trying to re-connect while still disconnecting", socketName);
|
||||
}
|
||||
Future<Session> futureConnect = null;
|
||||
try {
|
||||
URI destUri = URI.create("ws://" + ip + ":" + port + "/core");
|
||||
client.start();
|
||||
logger.debug("Trying to connect {} to {}", socketName, destUri);
|
||||
futureConnect = client.connect(this, destUri);
|
||||
futureConnect.get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
if (futureConnect != null) {
|
||||
futureConnect.cancel(true);
|
||||
}
|
||||
connectionListener
|
||||
.connectionLost("Error while connecting: " + (e.getMessage() != null ? e.getMessage() : "unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
connectionState = ConnectionState.DISCONNECTING;
|
||||
client.stop();
|
||||
} catch (Exception e) {
|
||||
logger.debug("{} encountered an error while closing connection", socketName, e);
|
||||
}
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener registered in this method will be called when a corresponding message will be detected
|
||||
* on the Mycroft bus.
|
||||
*
|
||||
* @param messageType The message type to listen to.
|
||||
* @param listener The listener will receive a callback when the requested message type will be detected on the bus.
|
||||
*/
|
||||
public void registerListener(MessageType messageType, MycroftMessageListener<? extends BaseMessage> listener) {
|
||||
Set<MycroftMessageListener<? extends BaseMessage>> messageTypeListeners = listeners.get(messageType);
|
||||
if (messageTypeListeners == null) {
|
||||
messageTypeListeners = new HashSet<MycroftMessageListener<? extends BaseMessage>>();
|
||||
listeners.put(messageType, messageTypeListeners);
|
||||
}
|
||||
messageTypeListeners.add(listener);
|
||||
}
|
||||
|
||||
public void unregisterListener(MessageType messageType, MycroftMessageListener<?> listener) {
|
||||
Optional.ofNullable(listeners.get(messageType))
|
||||
.ifPresent((messageTypeListeners) -> messageTypeListeners.remove(listener));
|
||||
}
|
||||
|
||||
public void sendMessage(BaseMessage message) throws IOException {
|
||||
sendMessage(gson.toJson(message));
|
||||
}
|
||||
|
||||
public void sendMessage(String message) throws IOException {
|
||||
final Session storedSession = this.session;
|
||||
try {
|
||||
if (storedSession != null) {
|
||||
storedSession.getRemote().sendString(message);
|
||||
} else {
|
||||
throw new IOException("Session is not initialized");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (storedSession != null && storedSession.isOpen()) {
|
||||
storedSession.close(-1, "Sending message error");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session session) {
|
||||
connectionState = ConnectionState.CONNECTED;
|
||||
logger.debug("{} successfully connected to {}: {}", socketName, session.getRemoteAddress().getAddress(),
|
||||
session.hashCode());
|
||||
connectionListener.connectionEstablished();
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(Session session, String message) {
|
||||
if (!session.equals(this.session)) {
|
||||
handleWrongSession(session, message);
|
||||
return;
|
||||
}
|
||||
logger.trace("{} received raw data: {}", socketName, message);
|
||||
|
||||
try {
|
||||
// get the base message information :
|
||||
BaseMessage mycroftMessage = gson.fromJson(message, BaseMessage.class);
|
||||
Objects.requireNonNull(mycroftMessage);
|
||||
// now that we have the message type, we can use a second and more precise parsing:
|
||||
if (mycroftMessage.type != MessageType.any) {
|
||||
mycroftMessage = gson.fromJson(message, mycroftMessage.type.getMessageTypeClass());
|
||||
Objects.requireNonNull(mycroftMessage);
|
||||
}
|
||||
// adding the raw message:
|
||||
mycroftMessage.message = message;
|
||||
|
||||
final BaseMessage finalMessage = mycroftMessage;
|
||||
Stream.concat(listeners.getOrDefault(MessageType.any, new HashSet<>()).stream(),
|
||||
listeners.getOrDefault(mycroftMessage.type, new HashSet<>()).stream()).forEach(listener -> {
|
||||
listener.baseMessageReceived(finalMessage);
|
||||
});
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
// we need to catch all processing exceptions, otherwise they could affect the connection
|
||||
logger.debug("{} encountered an error while processing the message {}: {}", socketName, message,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
public void onError(@Nullable Session session, Throwable cause) {
|
||||
|
||||
if (session == null || !session.equals(this.session)) {
|
||||
handleWrongSession(session, "Connection error: " + cause.getMessage());
|
||||
return;
|
||||
}
|
||||
logger.debug("{} connection error, closing: {}", socketName, cause.getMessage());
|
||||
|
||||
Session storedSession = this.session;
|
||||
if (storedSession != null && storedSession.isOpen()) {
|
||||
storedSession.close(-1, "Processing error");
|
||||
}
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void onClose(Session session, int statusCode, String reason) {
|
||||
if (!session.equals(this.session)) {
|
||||
handleWrongSession(session, "Connection closed: " + statusCode + " / " + reason);
|
||||
return;
|
||||
}
|
||||
logger.trace("{} closed connection: {} / {}", socketName, statusCode, reason);
|
||||
connectionState = ConnectionState.DISCONNECTED;
|
||||
this.session = null;
|
||||
connectionListener.connectionLost(reason);
|
||||
}
|
||||
|
||||
private void handleWrongSession(@Nullable Session session, String message) {
|
||||
if (session == null) {
|
||||
logger.debug("received and discarded message for null session : {}", message);
|
||||
} else {
|
||||
logger.debug("{} received and discarded message for other session {}: {}.", socketName, session.hashCode(),
|
||||
message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check connection state (successfully connected)
|
||||
*
|
||||
* @return true if connected, false if connecting, disconnecting or disconnected
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return connectionState == ConnectionState.CONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* used internally to represent the connection state
|
||||
*/
|
||||
private enum ConnectionState {
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING,
|
||||
DISCONNECTED
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Informs about the websocket connection.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface MycroftConnectionListener {
|
||||
/**
|
||||
* Connection successfully established.
|
||||
*/
|
||||
void connectionEstablished();
|
||||
|
||||
/**
|
||||
* Connection lost. A reconnect timer has been started.
|
||||
*
|
||||
* @param reason A reason for the disconnection
|
||||
*/
|
||||
void connectionLost(String reason);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
|
||||
/**
|
||||
* Informs about received messages
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface MycroftMessageListener<T extends BaseMessage> {
|
||||
/**
|
||||
* A new message was received
|
||||
*
|
||||
* @param message The received message
|
||||
*/
|
||||
void messageReceived(T message);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default void baseMessageReceived(BaseMessage baseMessage) {
|
||||
try {
|
||||
messageReceived(((T) baseMessage));
|
||||
} catch (ClassCastException cce) {
|
||||
throw new ClassCastException("Incorrect use of message in Mycroft binding");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This is the base message class for all messages circulating on the Mycroft bus.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class BaseMessage {
|
||||
|
||||
public MessageType type;
|
||||
public String message = "";
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to play the next title in
|
||||
* its underlying player.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public class MessageAudioNext extends BaseMessage {
|
||||
|
||||
public MessageAudioNext() {
|
||||
this.type = MessageType.mycroft_audio_service_next;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to pause
|
||||
* its underlying player.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageAudioPause extends BaseMessage {
|
||||
|
||||
public MessageAudioPause() {
|
||||
this.type = MessageType.mycroft_audio_service_pause;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to send the play command to
|
||||
* its underlying player.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class MessageAudioPlay extends BaseMessage {
|
||||
|
||||
public MessageAudioPlay() {
|
||||
this.type = MessageType.mycroft_audio_service_play;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to play the previous title in
|
||||
* its underlying player.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class MessageAudioPrev extends BaseMessage {
|
||||
|
||||
public MessageAudioPrev() {
|
||||
this.type = MessageType.mycroft_audio_service_prev;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to send resume command to
|
||||
* its underlying player
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class MessageAudioResume extends BaseMessage {
|
||||
|
||||
public MessageAudioResume() {
|
||||
this.type = MessageType.mycroft_audio_service_resume;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to stop
|
||||
* its underlying player.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageAudioStop extends BaseMessage {
|
||||
|
||||
public MessageAudioStop() {
|
||||
this.type = MessageType.mycroft_audio_service_stop;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to give information about
|
||||
* the title played on its underlying player.
|
||||
* Work in progress
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageAudioTrackInfo extends BaseMessage {
|
||||
|
||||
public MessageAudioTrackInfo() {
|
||||
this.type = MessageType.mycroft_audio_service_track_info;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message is sent by Mycroft to give information about
|
||||
* the title played on its underlying player.
|
||||
* Work in progress
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageAudioTrackInfoReply extends BaseMessage {
|
||||
|
||||
public MessageAudioTrackInfoReply() {
|
||||
this.type = MessageType.mycroft_audio_service_track_info_reply;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to begin to listen to the mic
|
||||
* and to try to do STT and intent recognition.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageMicListen extends BaseMessage {
|
||||
|
||||
public MessageMicListen() {
|
||||
this.type = MessageType.mycroft_mic_listen;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message informs the bus clients that Mycroft
|
||||
* is actively listening and trying to do STT.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageRecognizerLoopRecordBegin extends BaseMessage {
|
||||
|
||||
public Context context = new Context();
|
||||
|
||||
public MessageRecognizerLoopRecordBegin() {
|
||||
this.type = MessageType.recognizer_loop__record_begin;
|
||||
}
|
||||
|
||||
public static class Context {
|
||||
public String client_name = "";
|
||||
public String source = "";
|
||||
public String destination = "";
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message informs the bus clients that Mycroft
|
||||
* finished listening to the mic.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageRecognizerLoopRecordEnd extends BaseMessage {
|
||||
|
||||
public Context context = new Context();
|
||||
|
||||
public MessageRecognizerLoopRecordEnd() {
|
||||
this.type = MessageType.recognizer_loop__record_end;
|
||||
}
|
||||
|
||||
public static class Context {
|
||||
public String client_name = "";
|
||||
public String source = "";
|
||||
public String destination = "";
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message is sent to the skills
|
||||
* module to trigger an intent from a text.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageRecognizerLoopUtterance extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
public Context context = new Context();
|
||||
|
||||
public MessageRecognizerLoopUtterance() {
|
||||
this.type = MessageType.recognizer_loop__utterance;
|
||||
}
|
||||
|
||||
public MessageRecognizerLoopUtterance(String utterance) {
|
||||
this();
|
||||
this.data.utterances.add(utterance);
|
||||
this.context.client_name = "java_api";
|
||||
this.context.source = "audio";
|
||||
this.context.destination.add("skills");
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public List<String> utterances = new ArrayList<>();
|
||||
}
|
||||
|
||||
public static class Context {
|
||||
public String client_name = "";
|
||||
public String source = "";
|
||||
public List<String> destination = new ArrayList<>();
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message is sent to the Mycroft audio module
|
||||
* to trigger a TTS action.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageSpeak extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
public Context context = new Context();
|
||||
|
||||
public MessageSpeak() {
|
||||
this.type = MessageType.speak;
|
||||
}
|
||||
|
||||
public MessageSpeak(String textToSay) {
|
||||
this();
|
||||
this.data = new Data();
|
||||
this.data.utterance = textToSay;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public String utterance = "";
|
||||
public String expect_response = "";
|
||||
};
|
||||
|
||||
public static class Context {
|
||||
public String client_name = "";
|
||||
public List<String> source = new ArrayList<>();
|
||||
public String destination = "";
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to decrease the volume by 10%
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageVolumeDecrease extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
public MessageVolumeDecrease() {
|
||||
this.type = MessageType.mycroft_volume_decrease;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public Boolean play_sound = true;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message is sent by Mycroft to signal that the volume
|
||||
* is ducked during a STT recognition process.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageVolumeDuck extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
public Context context = new Context();
|
||||
|
||||
public MessageVolumeDuck() {
|
||||
this.type = MessageType.mycroft_volume_duck;
|
||||
}
|
||||
|
||||
public static final class Data {
|
||||
}
|
||||
|
||||
public static final class Context {
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to answer with the current volume
|
||||
* NOT FUNCTIONAL
|
||||
* (see https://community.mycroft.ai/t/openhab-plugin-development-audio-volume-message-types-missing/10576)
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageVolumeGet extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
public Context context = new Context();
|
||||
|
||||
public MessageVolumeGet() {
|
||||
this.type = MessageType.mycroft_volume_get;
|
||||
}
|
||||
|
||||
public static final class Data {
|
||||
}
|
||||
|
||||
public static final class Context {
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message is sent in response to a VolumeGet message
|
||||
* with the current volume in Mycroft
|
||||
* NOT FUNCTIONAL
|
||||
* (see https://community.mycroft.ai/t/openhab-plugin-development-audio-volume-message-types-missing/10576)
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageVolumeGetResponse extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
public MessageVolumeGetResponse() {
|
||||
this.type = MessageType.mycroft_volume_get_response;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public float percent = 0;
|
||||
public Boolean muted = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to increase the volume by 10%
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageVolumeIncrease extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
public MessageVolumeIncrease() {
|
||||
this.type = MessageType.mycroft_volume_increase;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public Boolean play_sound = true;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to mute the volume
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageVolumeMute extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
public MessageVolumeMute() {
|
||||
this.type = MessageType.mycroft_volume_mute;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public Boolean speak_message = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks IN THEORY Mycroft to set the volume to an amount
|
||||
* specified in the data payload.
|
||||
* But it seems in fact to be a message to inform third party of a
|
||||
* volume change
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageVolumeSet extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
public MessageVolumeSet() {
|
||||
this.type = MessageType.mycroft_volume_set;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public float percent = 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message is sent by Mycroft to signal that the volume
|
||||
* is no longer ducked after a STT recognition process.
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageVolumeUnduck extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
public Context context = new Context();
|
||||
|
||||
public MessageVolumeUnduck() {
|
||||
this.type = MessageType.mycroft_volume_unduck;
|
||||
}
|
||||
|
||||
public static final class Data {
|
||||
}
|
||||
|
||||
public static final class Context {
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api.dto;
|
||||
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
|
||||
/**
|
||||
* This message asks Mycroft to unmute the volume
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
public class MessageVolumeUnmute extends BaseMessage {
|
||||
|
||||
public Data data = new Data();
|
||||
|
||||
public MessageVolumeUnmute() {
|
||||
this.type = MessageType.mycroft_volume_unmute;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public Boolean speak_message = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.channels;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.MycroftBindingConstants;
|
||||
import org.openhab.binding.mycroft.internal.MycroftHandler;
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioNext;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioPause;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioPlay;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioPrev;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageAudioResume;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* This channel handles the Mycroft capability to act as a music player
|
||||
* (depending on common play music skills installed)
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AudioPlayerChannel extends MycroftChannel<State> {
|
||||
|
||||
public AudioPlayerChannel(MycroftHandler handler) {
|
||||
super(handler, MycroftBindingConstants.PLAYER_CHANNEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<MessageType> getMessageToListenTo() {
|
||||
return Arrays.asList(MessageType.mycroft_audio_service_prev, MessageType.mycroft_audio_service_next,
|
||||
MessageType.mycroft_audio_service_pause, MessageType.mycroft_audio_service_resume,
|
||||
MessageType.mycroft_audio_service_play, MessageType.mycroft_audio_service_stop,
|
||||
MessageType.mycroft_audio_service_track_info, MessageType.mycroft_audio_service_track_info_reply);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(BaseMessage message) {
|
||||
switch (message.type) {
|
||||
case mycroft_audio_service_pause:
|
||||
case mycroft_audio_service_stop:
|
||||
updateMyState(PlayPauseType.PAUSE);
|
||||
break;
|
||||
case mycroft_audio_service_play:
|
||||
case mycroft_audio_service_resume:
|
||||
updateMyState(PlayPauseType.PLAY);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(Command command) {
|
||||
if (command instanceof PlayPauseType) {
|
||||
if (((PlayPauseType) command) == PlayPauseType.PAUSE) {
|
||||
if (handler.sendMessage(new MessageAudioPause())) {
|
||||
updateMyState(PlayPauseType.PAUSE);
|
||||
}
|
||||
}
|
||||
if (((PlayPauseType) command) == PlayPauseType.PLAY) {
|
||||
handler.sendMessage(new MessageAudioPlay());
|
||||
if (handler.sendMessage(new MessageAudioResume())) {
|
||||
updateMyState(PlayPauseType.PLAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (command instanceof NextPreviousType) {
|
||||
if (((NextPreviousType) command) == NextPreviousType.NEXT) {
|
||||
if (handler.sendMessage(new MessageAudioNext())) {
|
||||
updateMyState(PlayPauseType.PLAY);
|
||||
}
|
||||
}
|
||||
if (((NextPreviousType) command) == NextPreviousType.PREVIOUS) {
|
||||
if (handler.sendMessage(new MessageAudioPrev())) {
|
||||
updateMyState(PlayPauseType.PLAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.channels;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* Interface for channel which can handle command
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ChannelCommandHandler {
|
||||
|
||||
public void handleCommand(Command command);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.channels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.MycroftBindingConstants;
|
||||
import org.openhab.binding.mycroft.internal.MycroftHandler;
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The channel responsible for sending/receiving raw message
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FullMessageChannel extends MycroftChannel<StringType> {
|
||||
|
||||
private List<String> messageTypesList = new ArrayList<>();
|
||||
|
||||
public FullMessageChannel(MycroftHandler handler, String messageTypesList) {
|
||||
super(handler, MycroftBindingConstants.FULL_MESSAGE_CHANNEL);
|
||||
for (String messageType : messageTypesList.split(",")) {
|
||||
this.messageTypesList.add(messageType.trim());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MessageType> getMessageToListenTo() {
|
||||
return Arrays.asList(MessageType.any);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(BaseMessage message) {
|
||||
if (messageTypesList.contains(message.type.getMessageTypeName())) {
|
||||
updateMyState(new StringType(message.message));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(Command command) {
|
||||
if (command instanceof StringType) {
|
||||
if (handler.sendMessage(command.toFullString())) {
|
||||
updateMyState(new StringType(command.toFullString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.channels;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.MycroftBindingConstants;
|
||||
import org.openhab.binding.mycroft.internal.MycroftHandler;
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageMicListen;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The channel responsible for triggering STT recognition
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ListenChannel extends MycroftChannel<OnOffType> {
|
||||
|
||||
public ListenChannel(MycroftHandler handler) {
|
||||
super(handler, MycroftBindingConstants.LISTEN_CHANNEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MessageType> getMessageToListenTo() {
|
||||
return Arrays.asList(MessageType.recognizer_loop__record_begin, MessageType.recognizer_loop__record_end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(BaseMessage message) {
|
||||
if (message.type == MessageType.recognizer_loop__record_begin) {
|
||||
updateMyState(OnOffType.ON);
|
||||
} else if (message.type == MessageType.recognizer_loop__record_end) {
|
||||
updateMyState(OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(Command command) {
|
||||
if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.ON) {
|
||||
handler.sendMessage(new MessageMicListen());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.channels;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.MycroftBindingConstants;
|
||||
import org.openhab.binding.mycroft.internal.MycroftHandler;
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeMute;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeSet;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeUnmute;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The channel responsible for muting the Mycroft speaker
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MuteChannel extends MycroftChannel<OnOffType> {
|
||||
|
||||
private int volumeRestorationLevel;
|
||||
|
||||
public MuteChannel(MycroftHandler handler, int volumeRestorationLevel) {
|
||||
super(handler, MycroftBindingConstants.VOLUME_MUTE_CHANNEL);
|
||||
this.volumeRestorationLevel = volumeRestorationLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MessageType> getMessageToListenTo() {
|
||||
// we don't listen to mute/unmute message because duck/unduck seems sufficient
|
||||
// and we don't want to change state twice for the same event
|
||||
// but it should be tested on mark I, as volume is handled differently
|
||||
return Arrays.asList(MessageType.mycroft_volume_duck, MessageType.mycroft_volume_unduck,
|
||||
MessageType.mycroft_volume_set, MessageType.mycroft_volume_increase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(BaseMessage message) {
|
||||
switch (message.type) {
|
||||
case mycroft_volume_mute:
|
||||
case mycroft_volume_duck:
|
||||
updateMyState(OnOffType.ON);
|
||||
break;
|
||||
case mycroft_volume_unmute:
|
||||
case mycroft_volume_unduck:
|
||||
case mycroft_volume_increase:
|
||||
updateMyState(OnOffType.OFF);
|
||||
break;
|
||||
case mycroft_volume_set:
|
||||
if (((MessageVolumeSet) message).data.percent > 0) {
|
||||
updateMyState(OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sendVolumeSetMessage(float volume) {
|
||||
String messageToSend = VolumeChannel.VOLUME_SETTER_MESSAGE.replaceAll("\\$\\$VOLUME",
|
||||
Float.valueOf(volume).toString());
|
||||
return handler.sendMessage(messageToSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(Command command) {
|
||||
if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.ON) {
|
||||
if (handler.sendMessage(new MessageVolumeMute())) {
|
||||
updateMyState(OnOffType.ON);
|
||||
}
|
||||
} else if (command == OnOffType.OFF) {
|
||||
if (handler.sendMessage(new MessageVolumeUnmute())) {
|
||||
updateMyState(OnOffType.OFF);
|
||||
// if configured, we can restore the volume to a fixed amount
|
||||
// usefull as a workaround for the broken Mycroft volume behavior
|
||||
if (volumeRestorationLevel > 0) {
|
||||
// we must wait 100ms for Mycroft to handle the message and
|
||||
// setting old volume before forcing to our value
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
sendVolumeSetMessage(Float.valueOf(volumeRestorationLevel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.channels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.MycroftHandler;
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
import org.openhab.binding.mycroft.internal.api.MycroftMessageListener;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* A helper method for channel handling
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class MycroftChannel<T extends State>
|
||||
implements ChannelCommandHandler, MycroftMessageListener<BaseMessage> {
|
||||
|
||||
private ChannelUID channelUID;
|
||||
protected MycroftHandler handler;
|
||||
|
||||
public MycroftChannel(MycroftHandler handler, String channelUIDPart) {
|
||||
this.handler = handler;
|
||||
this.channelUID = new ChannelUID(handler.getThing().getUID(), channelUIDPart);
|
||||
}
|
||||
|
||||
public final ChannelUID getChannelUID() {
|
||||
return channelUID;
|
||||
}
|
||||
|
||||
protected final void updateMyState(T state) {
|
||||
handler.updateMyChannel(this, state);
|
||||
}
|
||||
|
||||
public final void registerListeners() {
|
||||
for (MessageType messageType : getMessageToListenTo()) {
|
||||
handler.registerMessageListener(messageType, this);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<MessageType> getMessageToListenTo() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public final void unregisterListeners() {
|
||||
for (MessageType messageType : getMessageToListenTo()) {
|
||||
handler.unregisterMessageListener(messageType, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(BaseMessage message) {
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.channels;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.MycroftBindingConstants;
|
||||
import org.openhab.binding.mycroft.internal.MycroftHandler;
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageSpeak;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* The channel responsible for TSS
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SpeakChannel extends MycroftChannel<StringType> {
|
||||
|
||||
public SpeakChannel(MycroftHandler handler) {
|
||||
super(handler, MycroftBindingConstants.SPEAK_CHANNEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MessageType> getMessageToListenTo() {
|
||||
return Arrays.asList(MessageType.speak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(BaseMessage message) {
|
||||
if (message.type == MessageType.speak) {
|
||||
MessageSpeak messageSpeak = (MessageSpeak) message;
|
||||
updateMyState(new StringType(messageSpeak.data.utterance));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(Command command) {
|
||||
if (command instanceof StringType) {
|
||||
if (handler.sendMessage(new MessageSpeak(command.toFullString()))) {
|
||||
updateMyState(new StringType(command.toFullString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.channels;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.MycroftBindingConstants;
|
||||
import org.openhab.binding.mycroft.internal.MycroftHandler;
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageRecognizerLoopUtterance;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* This channel handle the full utterance send or received by Mycroft, before any intent recognition
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UtteranceChannel extends MycroftChannel<StringType> {
|
||||
|
||||
public UtteranceChannel(MycroftHandler handler) {
|
||||
super(handler, MycroftBindingConstants.UTTERANCE_CHANNEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<MessageType> getMessageToListenTo() {
|
||||
return Arrays.asList(MessageType.recognizer_loop__utterance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(BaseMessage message) {
|
||||
if (message.type == MessageType.recognizer_loop__utterance) {
|
||||
List<String> utterances = ((MessageRecognizerLoopUtterance) message).data.utterances;
|
||||
if (!utterances.isEmpty()) {
|
||||
updateMyState(new StringType(utterances.get(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(Command command) {
|
||||
if (command instanceof StringType) {
|
||||
if (handler.sendMessage(new MessageRecognizerLoopUtterance(command.toFullString()))) {
|
||||
updateMyState(new StringType(command.toFullString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.channels;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mycroft.internal.MycroftBindingConstants;
|
||||
import org.openhab.binding.mycroft.internal.MycroftHandler;
|
||||
import org.openhab.binding.mycroft.internal.api.MessageType;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeDecrease;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeGet;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeGetResponse;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeIncrease;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageVolumeSet;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The channel responsible for handling the volume of the Mycroft speaker
|
||||
* QUITE FUNCTIONAL but with workaround
|
||||
* (see https://community.mycroft.ai/t/openhab-plugin-development-audio-volume-message-types-missing/10576
|
||||
* and https://github.com/MycroftAI/skill-volume/issues/53)
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumeChannel extends MycroftChannel<State> {
|
||||
|
||||
/**
|
||||
* As the MessageVolumeSet is, contrary to the documentation, not listened to by Mycroft,
|
||||
* we use a workaround and send a message simulating an intent detection
|
||||
*/
|
||||
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\"}}";
|
||||
|
||||
private PercentType lastVolume = new PercentType(50);
|
||||
private PercentType lastNonZeroVolume = new PercentType(50);
|
||||
|
||||
public VolumeChannel(MycroftHandler handler) {
|
||||
super(handler, MycroftBindingConstants.VOLUME_CHANNEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MessageType> getMessageToListenTo() {
|
||||
// we don't listen to mute/unmute message because duck/unduck seems sufficient
|
||||
// and we don't want to change state twice for the same event
|
||||
// but it should be tested on mark I, as volume is handled differently
|
||||
return Arrays.asList(MessageType.mycroft_volume_get_response, MessageType.mycroft_volume_set,
|
||||
MessageType.mycroft_volume_increase, MessageType.mycroft_volume_decrease,
|
||||
MessageType.mycroft_volume_duck, MessageType.mycroft_volume_unduck);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(BaseMessage message) {
|
||||
|
||||
if (message.type == MessageType.mycroft_volume_get_response) {
|
||||
float volumeGet = ((MessageVolumeGetResponse) message).data.percent;
|
||||
updateAndSaveMyState(normalizeVolume(volumeGet));
|
||||
} else if (message.type == MessageType.mycroft_volume_set) {
|
||||
float volumeSet = ((MessageVolumeSet) message).data.percent;
|
||||
updateAndSaveMyState(normalizeVolume(volumeSet));
|
||||
} else if (message.type == MessageType.mycroft_volume_duck) {
|
||||
updateAndSaveMyState(new PercentType(0));
|
||||
} else if (message.type == MessageType.mycroft_volume_unduck) {
|
||||
updateAndSaveMyState(lastNonZeroVolume);
|
||||
} else if (message.type == MessageType.mycroft_volume_increase) {
|
||||
updateAndSaveMyState(normalizeVolume(lastVolume.intValue() + 10));
|
||||
} else if (message.type == MessageType.mycroft_volume_decrease) {
|
||||
updateAndSaveMyState(normalizeVolume(lastVolume.intValue() - 10));
|
||||
}
|
||||
}
|
||||
|
||||
protected final void updateAndSaveMyState(State state) {
|
||||
if (state instanceof PercentType) {
|
||||
this.lastVolume = ((PercentType) state);
|
||||
if (((PercentType) state).intValue() > 0) {
|
||||
this.lastNonZeroVolume = ((PercentType) state);
|
||||
}
|
||||
}
|
||||
super.updateMyState(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Protection method for volume with
|
||||
* potentially wrong value.
|
||||
*
|
||||
* @param volume The requested volume, on a scale from 0 to 100.
|
||||
* Could be out of bond, then it will be corrected.
|
||||
* @return A safe volume in PercentType between 0 and 100
|
||||
*/
|
||||
private PercentType normalizeVolume(int volume) {
|
||||
if (volume >= 100) {
|
||||
return PercentType.HUNDRED;
|
||||
} else if (volume <= 0) {
|
||||
return PercentType.ZERO;
|
||||
} else {
|
||||
return new PercentType(volume);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Protection method for volume with
|
||||
* potentially wrong value.
|
||||
*
|
||||
* @param volume The requested volume, on a scale from 0 to 1.
|
||||
* @return A safe volume in PercentType between 0 and 100
|
||||
*/
|
||||
private PercentType normalizeVolume(float volume) {
|
||||
if (volume >= 1) {
|
||||
return PercentType.HUNDRED;
|
||||
} else if (volume <= 0) {
|
||||
return PercentType.ZERO;
|
||||
} else {
|
||||
return new PercentType(Math.round(volume * 100));
|
||||
}
|
||||
}
|
||||
|
||||
public float toMycroftVolume(PercentType percentType) {
|
||||
return Float.valueOf(percentType.intValue());
|
||||
}
|
||||
|
||||
public PercentType computeNewVolume(int valueAdded) {
|
||||
return new PercentType(lastVolume.intValue() + valueAdded);
|
||||
}
|
||||
|
||||
private boolean sendSetMessage(float volume) {
|
||||
String messageToSend = VOLUME_SETTER_MESSAGE.replaceAll("\\$\\$VOLUME", Float.valueOf(volume).toString());
|
||||
return handler.sendMessage(messageToSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(Command command) {
|
||||
if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.ON) {
|
||||
if (sendSetMessage(toMycroftVolume(lastNonZeroVolume))) {
|
||||
updateAndSaveMyState(lastNonZeroVolume);
|
||||
}
|
||||
}
|
||||
if (command == OnOffType.OFF) {
|
||||
if (sendSetMessage(0)) {
|
||||
updateAndSaveMyState(PercentType.ZERO);
|
||||
}
|
||||
}
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
if (command == IncreaseDecreaseType.INCREASE) {
|
||||
if (handler.sendMessage(new MessageVolumeIncrease())) {
|
||||
updateAndSaveMyState(computeNewVolume(10));
|
||||
}
|
||||
}
|
||||
if (command == IncreaseDecreaseType.DECREASE) {
|
||||
handler.sendMessage(new MessageVolumeDecrease());
|
||||
updateAndSaveMyState(computeNewVolume(-10));
|
||||
}
|
||||
} else if (command instanceof PercentType) {
|
||||
sendSetMessage(toMycroftVolume((PercentType) command));
|
||||
updateAndSaveMyState((PercentType) command);
|
||||
} else if (command instanceof RefreshType) {
|
||||
handler.sendMessage(new MessageVolumeGet());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="mycroft" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Mycroft Binding</name>
|
||||
<description>
|
||||
Connects to a Mycroft instance in order to receive information from, and send commands to it. Typical
|
||||
usage includes
|
||||
triggering Mycroft to listen (as if a wake word was detected), sending text for Mycroft to speak,
|
||||
reacting on some
|
||||
specific intent, command skills by faking a spoken utterance, etc.
|
||||
</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,34 @@
|
||||
# binding
|
||||
|
||||
binding.mycroft.name = Extension Mycroft
|
||||
binding.mycroft.description = Cette extension se connecte à une enceinte Mycroft pour recevoir des informations et envoyer des commandes. Parmi les usages typiques : déclencher l'écoute de Mycroft (comme si le mot de réveil avait été détecté), envoyer un texte pour qu'il soit énoncé, réagir à un Intent, commander à des Skills comme si une phrase avait été prononcée, etc.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.mycroft.mycroft.label = Mycroft
|
||||
thing-type.mycroft.mycroft.description = Une instance de Mycroft (Mark I/II, Picroft).
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.mycroft.mycroft.host.label = Nom d'hôte
|
||||
thing-type.config.mycroft.mycroft.host.description = Nom d'hôte de l'instance.
|
||||
thing-type.config.mycroft.mycroft.port.label = Port
|
||||
thing-type.config.mycroft.mycroft.port.description = Port du bus de message.
|
||||
thing-type.config.mycroft.mycroft.volume_restoration_level.label = Niveau du volume de restauration
|
||||
thing-type.config.mycroft.mycroft.volume_restoration_level.description = Quand le volume est restauré, force Mycroft a le régler à cette valeur.
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.mycroft.full-message-channel.label = Message complet
|
||||
channel-type.mycroft.full-message-channel.description = Le dernier message qui a été vu sur le bus de message.
|
||||
channel-type.mycroft.listen-channel.label = État de l'écoute
|
||||
channel-type.mycroft.listen-channel.description = Allumé quand Mycroft écoute activement. Peut du coup simuler le mot de réveil.
|
||||
channel-type.mycroft.speak-channel.label = Synthèse vocale
|
||||
channel-type.mycroft.speak-channel.description = Phrase énoncée par Mycroft.
|
||||
channel-type.mycroft.utterance-channel.label = Commande vocale
|
||||
channel-type.mycroft.utterance-channel.description = Commande vocale reçue par Mycroft.
|
||||
|
||||
# channel types config
|
||||
|
||||
channel-type.config.mycroft.full-message-channel.messageTypes.label = Filtre du canal Message complet
|
||||
channel-type.config.mycroft.full-message-channel.messageTypes.description = Le canal Message complet sera mis à jour uniquement pour ces types de messages (liste séparée par une virgule)
|
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mycroft"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="mycroft">
|
||||
<label>Mycroft</label>
|
||||
<description>A Mycroft instance</description>
|
||||
|
||||
<channels>
|
||||
<channel id="listen" typeId="listen-channel"/>
|
||||
<channel id="speak" typeId="speak-channel"/>
|
||||
<channel id="utterance" typeId="utterance-channel"/>
|
||||
<channel id="player" typeId="system.media-control"/>
|
||||
<channel id="volume" typeId="system.volume"/>
|
||||
<channel id="volume_mute" typeId="system.mute"/>
|
||||
<channel id="full_message" typeId="full-message-channel"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>Hostname</label>
|
||||
<description>This is the host to connect to (ip or hostname)</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="false" min="1" max="65535">
|
||||
<label>Port</label>
|
||||
<description>This is the port to connect to.</description>
|
||||
<default>8181</default>
|
||||
</parameter>
|
||||
<parameter name="volume_restoration_level" type="integer" required="false" min="1" max="100">
|
||||
<advanced>true</advanced>
|
||||
<label>Volume Restoration Level</label>
|
||||
<description>When unmuted, force Mycroft to restore volume to this value</description>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="listen-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Listen State</label>
|
||||
<description>Switch to ON when Mycroft is listening. Can simulate a wake work detection to trigger the STT.</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="speak-channel">
|
||||
<item-type>String</item-type>
|
||||
<label>TTS</label>
|
||||
<description>The last sentence Mycroft spoke.</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="utterance-channel">
|
||||
<item-type>String</item-type>
|
||||
<label>Utterance</label>
|
||||
<description>The last utterance Mycroft received.</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="full-message-channel" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Full Bus Message</label>
|
||||
<description>The last full message seen on the Mycroft Bus.</description>
|
||||
<config-description>
|
||||
<parameter name="messageTypes" type="text" required="false">
|
||||
<label>Full Message Channel Filter</label>
|
||||
<description>The full message channel will be updated on these message types only (comma separated value)</description>
|
||||
<default>message.type.1,message.type.2</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mycroft.internal.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.BaseMessage;
|
||||
import org.openhab.binding.mycroft.internal.api.dto.MessageSpeak;
|
||||
|
||||
/**
|
||||
* This class provides tests for mycroft binding
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.WARN)
|
||||
@NonNullByDefault
|
||||
public class MycroftConnectionTest {
|
||||
|
||||
private @Mock @NonNullByDefault({}) MycroftConnectionListener mycroftConnectionListener;
|
||||
private @Mock @NonNullByDefault({}) Session sessionMock;
|
||||
|
||||
@Test
|
||||
public void testConnectionOK() throws IOException {
|
||||
|
||||
MycroftConnection mycroftConnection = new MycroftConnection(mycroftConnectionListener, new WebSocketClient());
|
||||
Mockito.when(sessionMock.getRemoteAddress()).thenReturn(new InetSocketAddress(1234));
|
||||
mycroftConnection.onConnect(sessionMock);
|
||||
|
||||
Mockito.verify(mycroftConnectionListener, Mockito.times(1)).connectionEstablished();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnyListener() throws UnsupportedEncodingException, IOException {
|
||||
MycroftConnection mycroftConnection = new MycroftConnection(mycroftConnectionListener, new WebSocketClient());
|
||||
|
||||
Mockito.when(sessionMock.getRemoteAddress()).thenReturn(new InetSocketAddress(1234));
|
||||
mycroftConnection.onConnect(sessionMock);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MycroftMessageListener<MessageSpeak> mockListener = Mockito.mock(MycroftMessageListener.class);
|
||||
ArgumentCaptor<BaseMessage> argCaptorMessage = ArgumentCaptor.forClass(BaseMessage.class);
|
||||
|
||||
// given we register any listener
|
||||
mycroftConnection.registerListener(MessageType.any, mockListener);
|
||||
|
||||
// when we send speak message
|
||||
@SuppressWarnings("null")
|
||||
String speakMessageJson = new String(
|
||||
MycroftConnectionTest.class.getResourceAsStream("speak.json").readAllBytes(), "UTF-8");
|
||||
mycroftConnection.onMessage(sessionMock, speakMessageJson);
|
||||
|
||||
// then message is correctly received by listener
|
||||
Mockito.verify(mockListener, Mockito.times(1)).baseMessageReceived(ArgumentMatchers.any());
|
||||
Mockito.verify(mockListener).baseMessageReceived(argCaptorMessage.capture());
|
||||
|
||||
assertEquals(argCaptorMessage.getValue().message, speakMessageJson);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpeakListener() throws IOException {
|
||||
|
||||
MycroftConnection mycroftConnection = new MycroftConnection(mycroftConnectionListener, new WebSocketClient());
|
||||
|
||||
Mockito.when(sessionMock.getRemoteAddress()).thenReturn(new InetSocketAddress(1234));
|
||||
mycroftConnection.onConnect(sessionMock);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MycroftMessageListener<MessageSpeak> mockListener = Mockito.mock(MycroftMessageListener.class);
|
||||
ArgumentCaptor<MessageSpeak> argCaptorMessage = ArgumentCaptor.forClass(MessageSpeak.class);
|
||||
|
||||
// given we register speak listener
|
||||
mycroftConnection.registerListener(MessageType.speak, mockListener);
|
||||
|
||||
// when we send speak message
|
||||
@SuppressWarnings("null")
|
||||
String speakMessageJson = new String(
|
||||
MycroftConnectionTest.class.getResourceAsStream("speak.json").readAllBytes(), "UTF-8");
|
||||
mycroftConnection.onMessage(sessionMock, speakMessageJson);
|
||||
|
||||
// then message is correctly received by listener
|
||||
Mockito.verify(mockListener).baseMessageReceived(argCaptorMessage.capture());
|
||||
|
||||
assertEquals(argCaptorMessage.getValue().data.utterance, "coucou");
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"type": "speak", "data": {"utterance": "coucou", "expect_response": false, "meta": {"skill": "SpeakSkill"}, "is_error": false}, "context": {"client_name": "mycroft_cli", "source": ["skills"], "destination": "debug_cli"}}
|
0
bundles/org.openhab.binding.playstation/src/main/resources/OH-INF/i18n/playstation.properties
Normal file → Executable file
0
bundles/org.openhab.binding.playstation/src/main/resources/OH-INF/i18n/playstation.properties
Normal file → Executable file
@ -229,6 +229,7 @@
|
||||
<module>org.openhab.binding.mqtt.generic</module>
|
||||
<module>org.openhab.binding.mqtt.homeassistant</module>
|
||||
<module>org.openhab.binding.mqtt.homie</module>
|
||||
<module>org.openhab.binding.mycroft</module>
|
||||
<module>org.openhab.binding.myq</module>
|
||||
<module>org.openhab.binding.mystrom</module>
|
||||
<module>org.openhab.binding.nanoleaf</module>
|
||||
|
Loading…
Reference in New Issue
Block a user