mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[amplipi] Add discovery and PA support (#11586)
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
parent
b80f41f3b8
commit
59444937bf
@ -1,6 +1,6 @@
|
||||
# AmpliPi Binding
|
||||
|
||||
This binding supports the multi room audio system [AmpliPi](http://www.amplipi.com/) from [MicroNova](http://www.micro-nova.com/).
|
||||
This binding supports the multi-room audio system [AmpliPi](http://www.amplipi.com/) from [MicroNova](http://www.micro-nova.com/).
|
||||
|
||||
|
||||
## Supported Things
|
||||
@ -10,7 +10,7 @@ Every available zone as well as group is managed as an individual Thing of type
|
||||
|
||||
## Discovery
|
||||
|
||||
Once the AmpliPi announces itself through mDNS (still a pending feature), it will be automatically discovered on the network.
|
||||
The AmpliPi announces itself through mDNS, so that the bindings is able to find it automatically.
|
||||
|
||||
As soon as the AmpliPi is online, its zones and groups are automatically retrieved and added as Things to the Inbox.
|
||||
|
||||
@ -45,6 +45,12 @@ The `zone` and `group` Things have the following channels:
|
||||
| mute | Switch | Mutes the zone/group |
|
||||
| source | Number | The source (1-4) that this zone/group is playing |
|
||||
|
||||
## Audio Sink
|
||||
|
||||
For every AmpliPi controller, an audio sink is registered with the id of the thing.
|
||||
This audio sink accepts urls and audio files to be played.
|
||||
It uses the AmpliPi's PA feature for announcements on all available zones.
|
||||
If no volume value is passed, the current volume of each zone is used, otherwise the provided volume is temporarily set on all zones for the announcement.
|
||||
|
||||
## Full Example
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.amplipi.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* A PA-like Announcement IF no zones or groups are specified, all available zones are used
|
||||
**/
|
||||
public class Announcement {
|
||||
|
||||
/**
|
||||
* URL to media to play as the announcement
|
||||
**/
|
||||
private String media;
|
||||
|
||||
/**
|
||||
* Output volume in dB
|
||||
**/
|
||||
private Integer vol = -40;
|
||||
|
||||
/**
|
||||
* Source to announce with
|
||||
**/
|
||||
private Integer sourceId = 3;
|
||||
|
||||
/**
|
||||
* Set of zone ids belonging to a group
|
||||
**/
|
||||
private List<Integer> zones = null;
|
||||
|
||||
/**
|
||||
* List of group ids
|
||||
**/
|
||||
private List<Integer> groups = null;
|
||||
|
||||
/**
|
||||
* URL to media to play as the announcement
|
||||
*
|
||||
* @return media
|
||||
**/
|
||||
@JsonProperty("media")
|
||||
public String getMedia() {
|
||||
return media;
|
||||
}
|
||||
|
||||
public void setMedia(String media) {
|
||||
this.media = media;
|
||||
}
|
||||
|
||||
public Announcement media(String media) {
|
||||
this.media = media;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output volume in dB
|
||||
* minimum: -79
|
||||
* maximum: 0
|
||||
*
|
||||
* @return vol
|
||||
**/
|
||||
@JsonProperty("vol")
|
||||
public Integer getVol() {
|
||||
return vol;
|
||||
}
|
||||
|
||||
public void setVol(Integer vol) {
|
||||
this.vol = vol;
|
||||
}
|
||||
|
||||
public Announcement vol(Integer vol) {
|
||||
this.vol = vol;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source to announce with
|
||||
* minimum: 0
|
||||
* maximum: 3
|
||||
*
|
||||
* @return sourceId
|
||||
**/
|
||||
@JsonProperty("source_id")
|
||||
public Integer getSourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
public void setSourceId(Integer sourceId) {
|
||||
this.sourceId = sourceId;
|
||||
}
|
||||
|
||||
public Announcement sourceId(Integer sourceId) {
|
||||
this.sourceId = sourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of zone ids belonging to a group
|
||||
*
|
||||
* @return zones
|
||||
**/
|
||||
@JsonProperty("zones")
|
||||
public List<Integer> getZones() {
|
||||
return zones;
|
||||
}
|
||||
|
||||
public void setZones(List<Integer> zones) {
|
||||
this.zones = zones;
|
||||
}
|
||||
|
||||
public Announcement zones(List<Integer> zones) {
|
||||
this.zones = zones;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Announcement addZonesItem(Integer zonesItem) {
|
||||
this.zones.add(zonesItem);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of group ids
|
||||
*
|
||||
* @return groups
|
||||
**/
|
||||
@JsonProperty("groups")
|
||||
public List<Integer> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setGroups(List<Integer> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
public Announcement groups(List<Integer> groups) {
|
||||
this.groups = groups;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Announcement addGroupsItem(Integer groupsItem) {
|
||||
this.groups.add(groupsItem);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class Announcement {\n");
|
||||
|
||||
sb.append(" media: ").append(toIndentedString(media)).append("\n");
|
||||
sb.append(" vol: ").append(toIndentedString(vol)).append("\n");
|
||||
sb.append(" sourceId: ").append(toIndentedString(sourceId)).append("\n");
|
||||
sb.append(" zones: ").append(toIndentedString(zones)).append("\n");
|
||||
sb.append(" groups: ").append(toIndentedString(groups)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private static String toIndentedString(Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.amplipi.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Reconfiguration of a specific Group
|
||||
**/
|
||||
public class GroupUpdateWithId {
|
||||
|
||||
/**
|
||||
* Friendly name
|
||||
**/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* id of the connected source
|
||||
**/
|
||||
private Integer sourceId;
|
||||
|
||||
/**
|
||||
* Set of zone ids belonging to a group
|
||||
**/
|
||||
private List<Integer> zones = null;
|
||||
|
||||
/**
|
||||
* Set to true if output is all zones muted
|
||||
**/
|
||||
private Boolean mute;
|
||||
|
||||
/**
|
||||
* Average input volume in dB
|
||||
**/
|
||||
private Integer volDelta;
|
||||
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* Friendly name
|
||||
*
|
||||
* @return name
|
||||
**/
|
||||
@JsonProperty("name")
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public GroupUpdateWithId name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* id of the connected source
|
||||
* minimum: 0
|
||||
* maximum: 3
|
||||
*
|
||||
* @return sourceId
|
||||
**/
|
||||
@JsonProperty("source_id")
|
||||
public Integer getSourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
public void setSourceId(Integer sourceId) {
|
||||
this.sourceId = sourceId;
|
||||
}
|
||||
|
||||
public GroupUpdateWithId sourceId(Integer sourceId) {
|
||||
this.sourceId = sourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of zone ids belonging to a group
|
||||
*
|
||||
* @return zones
|
||||
**/
|
||||
@JsonProperty("zones")
|
||||
public List<Integer> getZones() {
|
||||
return zones;
|
||||
}
|
||||
|
||||
public void setZones(List<Integer> zones) {
|
||||
this.zones = zones;
|
||||
}
|
||||
|
||||
public GroupUpdateWithId zones(List<Integer> zones) {
|
||||
this.zones = zones;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GroupUpdateWithId addZonesItem(Integer zonesItem) {
|
||||
this.zones.add(zonesItem);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if output is all zones muted
|
||||
*
|
||||
* @return mute
|
||||
**/
|
||||
@JsonProperty("mute")
|
||||
public Boolean getMute() {
|
||||
return mute;
|
||||
}
|
||||
|
||||
public void setMute(Boolean mute) {
|
||||
this.mute = mute;
|
||||
}
|
||||
|
||||
public GroupUpdateWithId mute(Boolean mute) {
|
||||
this.mute = mute;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Average input volume in dB
|
||||
* minimum: -79
|
||||
* maximum: 0
|
||||
*
|
||||
* @return volDelta
|
||||
**/
|
||||
@JsonProperty("vol_delta")
|
||||
public Integer getVolDelta() {
|
||||
return volDelta;
|
||||
}
|
||||
|
||||
public void setVolDelta(Integer volDelta) {
|
||||
this.volDelta = volDelta;
|
||||
}
|
||||
|
||||
public GroupUpdateWithId volDelta(Integer volDelta) {
|
||||
this.volDelta = volDelta;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
* @return id
|
||||
**/
|
||||
@JsonProperty("id")
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public GroupUpdateWithId id(Integer id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class GroupUpdateWithId {\n");
|
||||
|
||||
sb.append(" name: ").append(toIndentedString(name)).append("\n");
|
||||
sb.append(" sourceId: ").append(toIndentedString(sourceId)).append("\n");
|
||||
sb.append(" zones: ").append(toIndentedString(zones)).append("\n");
|
||||
sb.append(" mute: ").append(toIndentedString(mute)).append("\n");
|
||||
sb.append(" volDelta: ").append(toIndentedString(volDelta)).append("\n");
|
||||
sb.append(" id: ").append(toIndentedString(id)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private static String toIndentedString(Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.amplipi.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Reconfiguration of multiple zones specified by zone_ids and group_ids
|
||||
**/
|
||||
public class MultiZoneUpdate {
|
||||
|
||||
/**
|
||||
* Set of zone ids belonging to a group
|
||||
**/
|
||||
private List<Integer> zones = null;
|
||||
|
||||
/**
|
||||
* List of group ids
|
||||
**/
|
||||
private List<Integer> groups = null;
|
||||
|
||||
private ZoneUpdate update;
|
||||
|
||||
/**
|
||||
* Set of zone ids belonging to a group
|
||||
*
|
||||
* @return zones
|
||||
**/
|
||||
@JsonProperty("zones")
|
||||
public List<Integer> getZones() {
|
||||
return zones;
|
||||
}
|
||||
|
||||
public void setZones(List<Integer> zones) {
|
||||
this.zones = zones;
|
||||
}
|
||||
|
||||
public MultiZoneUpdate zones(List<Integer> zones) {
|
||||
this.zones = zones;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MultiZoneUpdate addZonesItem(Integer zonesItem) {
|
||||
this.zones.add(zonesItem);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of group ids
|
||||
*
|
||||
* @return groups
|
||||
**/
|
||||
@JsonProperty("groups")
|
||||
public List<Integer> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setGroups(List<Integer> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
public MultiZoneUpdate groups(List<Integer> groups) {
|
||||
this.groups = groups;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MultiZoneUpdate addGroupsItem(Integer groupsItem) {
|
||||
this.groups.add(groupsItem);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get update
|
||||
*
|
||||
* @return update
|
||||
**/
|
||||
@JsonProperty("update")
|
||||
public ZoneUpdate getUpdate() {
|
||||
return update;
|
||||
}
|
||||
|
||||
public void setUpdate(ZoneUpdate update) {
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
public MultiZoneUpdate update(ZoneUpdate update) {
|
||||
this.update = update;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class MultiZoneUpdate {\n");
|
||||
|
||||
sb.append(" zones: ").append(toIndentedString(zones)).append("\n");
|
||||
sb.append(" groups: ").append(toIndentedString(groups)).append("\n");
|
||||
sb.append(" update: ").append(toIndentedString(update)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private static String toIndentedString(Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.amplipi.internal.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class SourceInfo {
|
||||
|
||||
private String name;
|
||||
|
||||
private String state;
|
||||
|
||||
private String artist;
|
||||
|
||||
private String track;
|
||||
|
||||
private String album;
|
||||
|
||||
private String station;
|
||||
|
||||
private String imgUrl;
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return name
|
||||
**/
|
||||
@JsonProperty("name")
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public SourceInfo name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get state
|
||||
*
|
||||
* @return state
|
||||
**/
|
||||
@JsonProperty("state")
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public SourceInfo state(String state) {
|
||||
this.state = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get artist
|
||||
*
|
||||
* @return artist
|
||||
**/
|
||||
@JsonProperty("artist")
|
||||
public String getArtist() {
|
||||
return artist;
|
||||
}
|
||||
|
||||
public void setArtist(String artist) {
|
||||
this.artist = artist;
|
||||
}
|
||||
|
||||
public SourceInfo artist(String artist) {
|
||||
this.artist = artist;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get track
|
||||
*
|
||||
* @return track
|
||||
**/
|
||||
@JsonProperty("track")
|
||||
public String getTrack() {
|
||||
return track;
|
||||
}
|
||||
|
||||
public void setTrack(String track) {
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
public SourceInfo track(String track) {
|
||||
this.track = track;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get album
|
||||
*
|
||||
* @return album
|
||||
**/
|
||||
@JsonProperty("album")
|
||||
public String getAlbum() {
|
||||
return album;
|
||||
}
|
||||
|
||||
public void setAlbum(String album) {
|
||||
this.album = album;
|
||||
}
|
||||
|
||||
public SourceInfo album(String album) {
|
||||
this.album = album;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get station
|
||||
*
|
||||
* @return station
|
||||
**/
|
||||
@JsonProperty("station")
|
||||
public String getStation() {
|
||||
return station;
|
||||
}
|
||||
|
||||
public void setStation(String station) {
|
||||
this.station = station;
|
||||
}
|
||||
|
||||
public SourceInfo station(String station) {
|
||||
this.station = station;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get imgUrl
|
||||
*
|
||||
* @return imgUrl
|
||||
**/
|
||||
@JsonProperty("img_url")
|
||||
public String getImgUrl() {
|
||||
return imgUrl;
|
||||
}
|
||||
|
||||
public void setImgUrl(String imgUrl) {
|
||||
this.imgUrl = imgUrl;
|
||||
}
|
||||
|
||||
public SourceInfo imgUrl(String imgUrl) {
|
||||
this.imgUrl = imgUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class SourceInfo {\n");
|
||||
|
||||
sb.append(" name: ").append(toIndentedString(name)).append("\n");
|
||||
sb.append(" state: ").append(toIndentedString(state)).append("\n");
|
||||
sb.append(" artist: ").append(toIndentedString(artist)).append("\n");
|
||||
sb.append(" track: ").append(toIndentedString(track)).append("\n");
|
||||
sb.append(" album: ").append(toIndentedString(album)).append("\n");
|
||||
sb.append(" station: ").append(toIndentedString(station)).append("\n");
|
||||
sb.append(" imgUrl: ").append(toIndentedString(imgUrl)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private static String toIndentedString(Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.amplipi.internal.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Partial reconfiguration of a specific audio Source
|
||||
**/
|
||||
public class SourceUpdateWithId {
|
||||
|
||||
/**
|
||||
* Friendly name
|
||||
**/
|
||||
private String name;
|
||||
|
||||
private String input;
|
||||
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* Friendly name
|
||||
*
|
||||
* @return name
|
||||
**/
|
||||
@JsonProperty("name")
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public SourceUpdateWithId name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input
|
||||
*
|
||||
* @return input
|
||||
**/
|
||||
@JsonProperty("input")
|
||||
public String getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
public void setInput(String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
public SourceUpdateWithId input(String input) {
|
||||
this.input = input;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id
|
||||
* minimum: 0
|
||||
* maximum: 4
|
||||
*
|
||||
* @return id
|
||||
**/
|
||||
@JsonProperty("id")
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public SourceUpdateWithId id(Integer id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class SourceUpdateWithId {\n");
|
||||
|
||||
sb.append(" name: ").append(toIndentedString(name)).append("\n");
|
||||
sb.append(" input: ").append(toIndentedString(input)).append("\n");
|
||||
sb.append(" id: ").append(toIndentedString(id)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private static String toIndentedString(Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.amplipi.internal.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Reconfiguration of a specific Zone
|
||||
**/
|
||||
public class ZoneUpdateWithId {
|
||||
|
||||
/**
|
||||
* Friendly name
|
||||
**/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* id of the connected source
|
||||
**/
|
||||
private Integer sourceId;
|
||||
|
||||
/**
|
||||
* Set to true if output is muted
|
||||
**/
|
||||
private Boolean mute;
|
||||
|
||||
/**
|
||||
* Output volume in dB
|
||||
**/
|
||||
private Integer vol;
|
||||
|
||||
/**
|
||||
* Set to true if not connected to a speaker
|
||||
**/
|
||||
private Boolean disabled;
|
||||
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* Friendly name
|
||||
*
|
||||
* @return name
|
||||
**/
|
||||
@JsonProperty("name")
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ZoneUpdateWithId name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* id of the connected source
|
||||
* minimum: 0
|
||||
* maximum: 3
|
||||
*
|
||||
* @return sourceId
|
||||
**/
|
||||
@JsonProperty("source_id")
|
||||
public Integer getSourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
public void setSourceId(Integer sourceId) {
|
||||
this.sourceId = sourceId;
|
||||
}
|
||||
|
||||
public ZoneUpdateWithId sourceId(Integer sourceId) {
|
||||
this.sourceId = sourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if output is muted
|
||||
*
|
||||
* @return mute
|
||||
**/
|
||||
@JsonProperty("mute")
|
||||
public Boolean getMute() {
|
||||
return mute;
|
||||
}
|
||||
|
||||
public void setMute(Boolean mute) {
|
||||
this.mute = mute;
|
||||
}
|
||||
|
||||
public ZoneUpdateWithId mute(Boolean mute) {
|
||||
this.mute = mute;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output volume in dB
|
||||
* minimum: -79
|
||||
* maximum: 0
|
||||
*
|
||||
* @return vol
|
||||
**/
|
||||
@JsonProperty("vol")
|
||||
public Integer getVol() {
|
||||
return vol;
|
||||
}
|
||||
|
||||
public void setVol(Integer vol) {
|
||||
this.vol = vol;
|
||||
}
|
||||
|
||||
public ZoneUpdateWithId vol(Integer vol) {
|
||||
this.vol = vol;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if not connected to a speaker
|
||||
*
|
||||
* @return disabled
|
||||
**/
|
||||
@JsonProperty("disabled")
|
||||
public Boolean getDisabled() {
|
||||
return disabled;
|
||||
}
|
||||
|
||||
public void setDisabled(Boolean disabled) {
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
public ZoneUpdateWithId disabled(Boolean disabled) {
|
||||
this.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id
|
||||
* minimum: 0
|
||||
* maximum: 35
|
||||
*
|
||||
* @return id
|
||||
**/
|
||||
@JsonProperty("id")
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ZoneUpdateWithId id(Integer id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class ZoneUpdateWithId {\n");
|
||||
|
||||
sb.append(" name: ").append(toIndentedString(name)).append("\n");
|
||||
sb.append(" sourceId: ").append(toIndentedString(sourceId)).append("\n");
|
||||
sb.append(" mute: ").append(toIndentedString(mute)).append("\n");
|
||||
sb.append(" vol: ").append(toIndentedString(vol)).append("\n");
|
||||
sb.append(" disabled: ").append(toIndentedString(disabled)).append("\n");
|
||||
sb.append(" id: ").append(toIndentedString(id)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private static String toIndentedString(Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
}
|
@ -12,16 +12,20 @@
|
||||
*/
|
||||
package org.openhab.binding.amplipi.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link AmpliPiConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AmpliPiConfiguration {
|
||||
|
||||
/**
|
||||
* Sample configuration parameters. Replace with your own.
|
||||
*/
|
||||
public String hostname;
|
||||
public @Nullable String hostname;
|
||||
public int refreshInterval;
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
@ -90,7 +89,7 @@ public class AmpliPiGroupHandler extends BaseThingHandler implements AmpliPiStat
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) {
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
// do nothing - we just wait for the next automatic refresh
|
||||
return;
|
||||
@ -134,7 +133,7 @@ public class AmpliPiGroupHandler extends BaseThingHandler implements AmpliPiStat
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(@NonNull Status status) {
|
||||
public void receive(Status status) {
|
||||
int id = getId(thing);
|
||||
Optional<Group> group = status.getGroups().stream().filter(z -> z.getId().equals(id)).findFirst();
|
||||
if (group.isPresent()) {
|
||||
|
@ -31,12 +31,16 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.amplipi.internal.audio.PAAudioSink;
|
||||
import org.openhab.binding.amplipi.internal.discovery.AmpliPiZoneAndGroupDiscoveryService;
|
||||
import org.openhab.binding.amplipi.internal.model.Announcement;
|
||||
import org.openhab.binding.amplipi.internal.model.Preset;
|
||||
import org.openhab.binding.amplipi.internal.model.SourceUpdate;
|
||||
import org.openhab.binding.amplipi.internal.model.Status;
|
||||
import org.openhab.binding.amplipi.internal.model.Stream;
|
||||
import org.openhab.core.audio.AudioHTTPServer;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@ -67,7 +71,9 @@ public class AmpliPiHandler extends BaseBridgeHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(AmpliPiHandler.class);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private AudioHTTPServer audioHTTPServer;
|
||||
private final Gson gson;
|
||||
private @Nullable String callbackUrl;
|
||||
|
||||
private String url = "http://amplipi";
|
||||
private List<Preset> presets = List.of();
|
||||
@ -76,9 +82,12 @@ public class AmpliPiHandler extends BaseBridgeHandler {
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
|
||||
public AmpliPiHandler(Thing thing, HttpClient httpClient) {
|
||||
public AmpliPiHandler(Thing thing, HttpClient httpClient, AudioHTTPServer audioHTTPServer,
|
||||
@Nullable String callbackUrl) {
|
||||
super((Bridge) thing);
|
||||
this.httpClient = httpClient;
|
||||
this.audioHTTPServer = audioHTTPServer;
|
||||
this.callbackUrl = callbackUrl;
|
||||
this.gson = new Gson();
|
||||
}
|
||||
|
||||
@ -190,7 +199,7 @@ public class AmpliPiHandler extends BaseBridgeHandler {
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Set.of(PresetCommandOptionProvider.class, InputStateOptionProvider.class,
|
||||
AmpliPiZoneAndGroupDiscoveryService.class);
|
||||
AmpliPiZoneAndGroupDiscoveryService.class, PAAudioSink.class);
|
||||
}
|
||||
|
||||
public List<Preset> getPresets() {
|
||||
@ -205,6 +214,10 @@ public class AmpliPiHandler extends BaseBridgeHandler {
|
||||
return url;
|
||||
}
|
||||
|
||||
public AudioHTTPServer getAudioHTTPServer() {
|
||||
return audioHTTPServer;
|
||||
}
|
||||
|
||||
public void addStatusChangeListener(AmpliPiStatusChangeListener listener) {
|
||||
changeListeners.add(listener);
|
||||
}
|
||||
@ -212,4 +225,31 @@ public class AmpliPiHandler extends BaseBridgeHandler {
|
||||
public void removeStatusChangeListener(AmpliPiStatusChangeListener listener) {
|
||||
changeListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void playPA(String audioUrl, @Nullable PercentType volume) {
|
||||
Announcement announcement = new Announcement();
|
||||
announcement.setMedia(audioUrl);
|
||||
if (volume != null) {
|
||||
announcement.setVol(AmpliPiUtils.percentTypeToVolume(volume));
|
||||
}
|
||||
String url = getUrl() + "/api/announce";
|
||||
StringContentProvider contentProvider = new StringContentProvider(gson.toJson(announcement));
|
||||
try {
|
||||
ContentResponse response = httpClient.newRequest(url).method(HttpMethod.POST)
|
||||
.content(contentProvider, "application/json").send();
|
||||
if (response.getStatus() != HttpStatus.OK_200) {
|
||||
logger.error("AmpliPi API returned HTTP status {}.", response.getStatus());
|
||||
logger.debug("Content: {}", response.getContentAsString());
|
||||
} else {
|
||||
logger.debug("PA request sent successfully.");
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"AmpliPi request failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getCallbackUrl() {
|
||||
return callbackUrl;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,10 @@ import java.util.Set;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.core.audio.AudioHTTPServer;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.net.HttpServiceUtil;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
@ -28,6 +31,8 @@ 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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AmpliPiHandlerFactory} is responsible for creating things and thing
|
||||
@ -39,11 +44,18 @@ import org.osgi.service.component.annotations.Reference;
|
||||
@Component(configurationPid = "binding.amplipi", service = ThingHandlerFactory.class)
|
||||
public class AmpliPiHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AmpliPiHandlerFactory.class);
|
||||
|
||||
private HttpClient httpClient;
|
||||
private AudioHTTPServer audioHttpServer;
|
||||
private final NetworkAddressService networkAddressService;
|
||||
|
||||
@Activate
|
||||
public AmpliPiHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
|
||||
public AmpliPiHandlerFactory(@Reference HttpClientFactory httpClientFactory,
|
||||
@Reference AudioHTTPServer audioHttpServer, @Reference NetworkAddressService networkAddressService) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.audioHttpServer = audioHttpServer;
|
||||
this.networkAddressService = networkAddressService;
|
||||
}
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_CONTROLLER, THING_TYPE_ZONE,
|
||||
@ -59,7 +71,8 @@ public class AmpliPiHandlerFactory extends BaseThingHandlerFactory {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_CONTROLLER.equals(thingTypeUID)) {
|
||||
return new AmpliPiHandler(thing, httpClient);
|
||||
String callbackUrl = createCallbackUrl();
|
||||
return new AmpliPiHandler(thing, httpClient, audioHttpServer, callbackUrl);
|
||||
}
|
||||
if (THING_TYPE_ZONE.equals(thingTypeUID)) {
|
||||
return new AmpliPiZoneHandler(thing, httpClient);
|
||||
@ -70,4 +83,21 @@ public class AmpliPiHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable String createCallbackUrl() {
|
||||
final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
|
||||
if (ipAddress == null) {
|
||||
logger.warn("No network interface could be found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// we do not use SSL as it can cause certificate validation issues.
|
||||
final int port = HttpServiceUtil.getHttpServicePort(bundleContext);
|
||||
if (port == -1) {
|
||||
logger.warn("Cannot find port of the http service.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return "http://" + ipAddress + ":" + port;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
@ -90,7 +89,7 @@ public class AmpliPiZoneHandler extends BaseThingHandler implements AmpliPiStatu
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) {
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
// do nothing - we just wait for the next automatic refresh
|
||||
return;
|
||||
@ -133,7 +132,7 @@ public class AmpliPiZoneHandler extends BaseThingHandler implements AmpliPiStatu
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(@NonNull Status status) {
|
||||
public void receive(Status status) {
|
||||
int id = getId(thing);
|
||||
Optional<Zone> zone = status.getZones().stream().filter(z -> z.getId().equals(id)).findFirst();
|
||||
if (zone.isPresent()) {
|
||||
|
@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.amplipi.internal.audio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amplipi.internal.AmpliPiHandler;
|
||||
import org.openhab.core.audio.AudioFormat;
|
||||
import org.openhab.core.audio.AudioSink;
|
||||
import org.openhab.core.audio.AudioStream;
|
||||
import org.openhab.core.audio.FixedLengthAudioStream;
|
||||
import org.openhab.core.audio.URLAudioStream;
|
||||
import org.openhab.core.audio.UnsupportedAudioFormatException;
|
||||
import org.openhab.core.audio.UnsupportedAudioStreamException;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is an audio sink that allows to do public announcements on the AmpliPi.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PAAudioSink implements AudioSink, ThingHandlerService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PAAudioSink.class);
|
||||
|
||||
private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Set.of(AudioFormat.MP3, AudioFormat.WAV);
|
||||
private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = Set
|
||||
.of(FixedLengthAudioStream.class, URLAudioStream.class);
|
||||
|
||||
private @Nullable AmpliPiHandler handler;
|
||||
|
||||
private @Nullable PercentType volume;
|
||||
|
||||
@Override
|
||||
public void process(@Nullable AudioStream audioStream)
|
||||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
|
||||
if (audioStream == null) {
|
||||
// in case the audioStream is null, this should be interpreted as a request to end any currently playing
|
||||
// stream.
|
||||
logger.debug("Web Audio sink does not support stopping the currently playing stream.");
|
||||
return;
|
||||
}
|
||||
AmpliPiHandler localHandler = this.handler;
|
||||
if (localHandler != null) {
|
||||
try (AudioStream stream = audioStream) {
|
||||
logger.debug("Received audio stream of format {}", audioStream.getFormat());
|
||||
String audioUrl;
|
||||
if (audioStream instanceof URLAudioStream) {
|
||||
// it is an external URL, so we can directly pass this on.
|
||||
URLAudioStream urlAudioStream = (URLAudioStream) audioStream;
|
||||
audioUrl = urlAudioStream.getURL();
|
||||
} else if (audioStream instanceof FixedLengthAudioStream) {
|
||||
String callbackUrl = localHandler.getCallbackUrl();
|
||||
if (callbackUrl == null) {
|
||||
throw new UnsupportedAudioStreamException(
|
||||
"Cannot play audio since no callback url is available.", audioStream.getClass());
|
||||
} else {
|
||||
// we need to serve it for a while, hence only
|
||||
// FixedLengthAudioStreams are supported.
|
||||
String relativeUrl = localHandler.getAudioHTTPServer()
|
||||
.serve((FixedLengthAudioStream) audioStream, 10).toString();
|
||||
audioUrl = callbackUrl + relativeUrl;
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedAudioStreamException(
|
||||
"Web audio sink can only handle FixedLengthAudioStreams and URLAudioStreams.",
|
||||
audioStream.getClass());
|
||||
}
|
||||
localHandler.playPA(audioUrl, volume);
|
||||
// we reset the volume value again, so that a next invocation without a volume will again use the zones
|
||||
// defaults.
|
||||
volume = null;
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error while closing the audio stream: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<AudioFormat> getSupportedFormats() {
|
||||
return SUPPORTED_AUDIO_FORMATS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends AudioStream>> getSupportedStreams() {
|
||||
return SUPPORTED_AUDIO_STREAMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
if (handler != null) {
|
||||
return handler.getThing().getUID().toString();
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getLabel(@Nullable Locale locale) {
|
||||
if (handler != null) {
|
||||
return handler.getThing().getLabel();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PercentType getVolume() throws IOException {
|
||||
PercentType vol = volume;
|
||||
if (vol != null) {
|
||||
return vol;
|
||||
} else {
|
||||
throw new IOException("Audio sink does not support reporting the volume.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVolume(final PercentType volume) throws IOException {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
this.handler = (AmpliPiHandler) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.amplipi.internal.discovery;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
@ -24,6 +25,7 @@ import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* This is a discovery participant which finds AmpliPis on the local network
|
||||
@ -33,8 +35,11 @@ import org.openhab.core.thing.ThingUID;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component
|
||||
public class AmpliPiMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private static final String AMPLIPI_API = "amplipi-api";
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Set.of(AmpliPiBindingConstants.THING_TYPE_CONTROLLER);
|
||||
@ -42,16 +47,15 @@ public class AmpliPiMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return "_http._tcp";
|
||||
return "_http._tcp.local.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
||||
ThingUID uid = getThingUID(service);
|
||||
if (uid != null) {
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(service.getName())
|
||||
.withProperty(AmpliPiBindingConstants.CFG_PARAM_HOSTNAME,
|
||||
service.getInet4Addresses()[0].getHostAddress())
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel("AmpliPi Controller")
|
||||
.withProperty(AmpliPiBindingConstants.CFG_PARAM_HOSTNAME, getIpAddress(service).getHostAddress())
|
||||
.withRepresentationProperty(AmpliPiBindingConstants.CFG_PARAM_HOSTNAME).build();
|
||||
return result;
|
||||
} else {
|
||||
@ -61,7 +65,21 @@ public class AmpliPiMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
||||
// TODO: Currently, the AmpliPi does not seem to announce any services.
|
||||
if (service.getName().equals(AMPLIPI_API)) {
|
||||
InetAddress ip = getIpAddress(service);
|
||||
if (ip != null) {
|
||||
String id = ip.toString().substring(1).replaceAll("\\.", "");
|
||||
return new ThingUID(AmpliPiBindingConstants.THING_TYPE_CONTROLLER, id);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable InetAddress getIpAddress(ServiceInfo service) {
|
||||
if (service.getInet4Addresses().length > 0) {
|
||||
return service.getInet4Addresses()[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ public class AmpliPiZoneAndGroupDiscoveryService extends AbstractDiscoveryServic
|
||||
if (handler != null) {
|
||||
ThingUID bridgeUID = handler.getThing().getUID();
|
||||
ThingUID uid = new ThingUID(AmpliPiBindingConstants.THING_TYPE_ZONE, bridgeUID, z.getId().toString());
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(z.getName())
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel("AmpliPi Zone '" + z.getName() + "'")
|
||||
.withProperty(AmpliPiBindingConstants.CFG_PARAM_ID, z.getId()).withBridge(bridgeUID)
|
||||
.withRepresentationProperty(AmpliPiBindingConstants.CFG_PARAM_ID).build();
|
||||
thingDiscovered(result);
|
||||
@ -89,7 +89,7 @@ public class AmpliPiZoneAndGroupDiscoveryService extends AbstractDiscoveryServic
|
||||
if (handler != null) {
|
||||
ThingUID bridgeUID = handler.getThing().getUID();
|
||||
ThingUID uid = new ThingUID(AmpliPiBindingConstants.THING_TYPE_GROUP, bridgeUID, g.getId().toString());
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(g.getName())
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel("AmpliPi Group '" + g.getName() + "'")
|
||||
.withProperty(AmpliPiBindingConstants.CFG_PARAM_ID, g.getId()).withBridge(bridgeUID)
|
||||
.withRepresentationProperty(AmpliPiBindingConstants.CFG_PARAM_ID).build();
|
||||
thingDiscovered(result);
|
||||
|
Loading…
Reference in New Issue
Block a user