[amplipi] Initial contribution of AmpliPi binding (#10983)

* Initial contribution of AmpliPi binding

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* change http client from cxf jax-rs to Jetty

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* applied spotless

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* Remove Jackson dependency

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* Add support for input handling

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* Clean up, improvements and documentation

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* Remove unused password from configuration class

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* Remove example properties file

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* revert change in .gitignore

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* Update README

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* Address review feedback

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* Handle ExecutionException as network error

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer 2021-08-01 12:25:29 +02:00 committed by GitHub
parent b468582e13
commit f25cc8d14a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 8849 additions and 0 deletions

View File

@ -20,6 +20,7 @@
/bundles/org.openhab.binding.amazondashbutton/ @OLibutzki
/bundles/org.openhab.binding.amazonechocontrol/ @mgeramb
/bundles/org.openhab.binding.ambientweather/ @mhilbush
/bundles/org.openhab.binding.amplipi/ @kaikreuzer
/bundles/org.openhab.binding.androiddebugbridge/ @GiviMAD
/bundles/org.openhab.binding.astro/ @gerrieg
/bundles/org.openhab.binding.atlona/ @tmrobert8

View File

@ -91,6 +91,11 @@
<artifactId>org.openhab.binding.ambientweather</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.amplipi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.androiddebugbridge</artifactId>

View 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

View File

@ -0,0 +1,91 @@
# AmpliPi Binding
This binding supports the multi room audio system [AmpliPi](http://www.amplipi.com/) from [MicroNova](http://www.micro-nova.com/).
## Supported Things
The AmpliPi itself is modeled as a Bridge of type `controller`.
Every available zone as well as group is managed as an individual Thing of type `zone` resp. `group`.
## Discovery
Once the AmpliPi announces itself through mDNS (still a pending feature), it will be automatically discovered on the network.
As soon as the AmpliPi is online, its zones and groups are automatically retrieved and added as Things to the Inbox.
## Thing Configuration
The `controller` Bridge has two configuration parameters:
| Parameter | Required | Description |
|-----------------|----------|----------------------------------------------------------------------------------------------------|
| hostname | yes | The hostname or IP address of the AmpliPi on the network |
| refreshInterval | no | The time to wait between two polling requests for receiving state updates. Defaults to 10 seconds. |
Both the `zone` and `group` Things only require a single configuration parameter `id`, which corresponds to their id on the AmpliPi.
## Channels
These are the channels of the `controller` Bridge:
| Channel | Type | Description |
|----------|--------|------------------------------------------------------------------------------------------------------|
| preset | Number | Allows setting a pre-configured preset. The available options are dynamically read from the AmpliPi. |
| input1 | String | The selected input of source 1 |
| input2 | String | The selected input of source 2 |
| input3 | String | The selected input of source 3 |
| input4 | String | The selected input of source 4 |
The `zone` and `group` Things have the following channels:
| Channel | Type | Description |
|----------|--------|----------------------------------------------------|
| volume | Dimmer | The volume of the zone/group |
| mute | Switch | Mutes the zone/group |
| source | Number | The source (1-4) that this zone/group is playing |
## Full Example
amplipi.things:
```
Bridge amplipi:controller:1 "My AmpliPi" [ hostname="amplipi.local" ] {
zone zone2 "Living Room" [ id=1 ]
}
```
amplipi.items:
```
Number Preset "Preset" { channel="amplipi:controller:1:preset" }
String Input1 "Input 1" { channel="amplipi:controller:1:input1" }
String Input2 "Input 2" { channel="amplipi:controller:1:input2" }
String Input3 "Input 3" { channel="amplipi:controller:1:input3" }
String Input4 "Input 4" { channel="amplipi:controller:1:input4" }
Dimmer VolumeZ2 "Volume Zone2" { channel="amplipi:zone:1:zone2:volume" }
Switch MuteZ2 "Mute Zone2" { channel="amplipi:zone:1:zone2::mute" }
Number SourceZ2 "Source Zone2" { channel="amplipi:zone:1:zone2::source" }
```
amplipi.sitemap:
```
sitemap amplipi label="Main Menu"
{
Frame label="AmpliPi" {
Selection item=Preset
Selection item=Input1
Selection item=Input2
Selection item=Input3
Selection item=Input4
}
Frame label="Living Room Zone" {
Slider item=VolumeZ2 label="Volume Zone 1 [%.1f %%]"
Switch item=MuteZ2
Selection item=SourceZ2
}
}
```

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
<?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.2.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.amplipi</artifactId>
<name>openHAB Add-ons :: Bundles :: AmpliPi Binding</name>
<properties>
<cxf-version>3.4.3</cxf-version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/gen/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>5.1.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/amplipi-api.yml</inputSpec>
<generatorName>jaxrs-cxf-client</generatorName>
<output>${project.basedir}</output>
<modelPackage>org.openhab.binding.amplipi.internal.model</modelPackage>
<skipValidateSpec>true</skipValidateSpec>
<generateApis>false</generateApis>
<generateApiTests>false</generateApiTests>
<generateSupportingFiles>false</generateSupportingFiles>
<generateApiDocumentation>false</generateApiDocumentation>
<generateModelDocumentation>false</generateModelDocumentation>
<skipOverwrite>true</skipOverwrite>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,95 @@
/**
* 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.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* A command to execute on a stream
**/
@Schema(description = "A command to execute on a stream ")
public class Command {
@Schema(required = true)
@SerializedName("stream_id")
/**
* Stream to execute the command on
**/
private Integer streamId;
@Schema(required = true)
/**
* Command to execute
**/
private String cmd;
/**
* Stream to execute the command on
*
* @return streamId
**/
public Integer getStreamId() {
return streamId;
}
public void setStreamId(Integer streamId) {
this.streamId = streamId;
}
public Command streamId(Integer streamId) {
this.streamId = streamId;
return this;
}
/**
* Command to execute
*
* @return cmd
**/
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public Command cmd(String cmd) {
this.cmd = cmd;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Command {\n");
sb.append(" streamId: ").append(toIndentedString(streamId)).append("\n");
sb.append(" cmd: ").append(toIndentedString(cmd)).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 ");
}
}

View File

@ -0,0 +1,209 @@
/**
* 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.LinkedHashSet;
import java.util.Set;
import com.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* A group of zones that can share the same audio input and be controlled as a group ie. Updstairs. Volume, mute, and
* source_id fields are aggregates of the member zones.
**/
@Schema(description = "A group of zones that can share the same audio input and be controlled as a group ie. Updstairs. Volume, mute, and source_id fields are aggregates of the member zones.")
public class Group {
@Schema
/**
* Unique identifier
**/
private Integer id;
@Schema(required = true)
/**
* Friendly name
**/
private String name;
@Schema
@SerializedName("source_id")
/**
* id of the connected source
**/
private Integer sourceId = 0;
@Schema(required = true)
/**
* Set of zones belonging to a group
**/
private Set<Integer> zones = new LinkedHashSet<Integer>();
@Schema
/**
* Set to true if output is all zones muted
**/
private Boolean mute = true;
@Schema
@SerializedName("vol_delta")
/**
* Average utput volume in dB
**/
private Integer volDelta = -79;
/**
* Unique identifier
*
* @return id
**/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Group id(Integer id) {
this.id = id;
return this;
}
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Group name(String name) {
this.name = name;
return this;
}
/**
* id of the connected source
* minimum: 0
* maximum: 3
*
* @return sourceId
**/
public Integer getSourceId() {
return sourceId;
}
public void setSourceId(Integer sourceId) {
this.sourceId = sourceId;
}
public Group sourceId(Integer sourceId) {
this.sourceId = sourceId;
return this;
}
/**
* Set of zones belonging to a group
*
* @return zones
**/
public Set<Integer> getZones() {
return zones;
}
public void setZones(Set<Integer> zones) {
this.zones = zones;
}
public Group zones(Set<Integer> zones) {
this.zones = zones;
return this;
}
public Group addZonesItem(Integer zonesItem) {
this.zones.add(zonesItem);
return this;
}
/**
* Set to true if output is all zones muted
*
* @return mute
**/
public Boolean getMute() {
return mute;
}
public void setMute(Boolean mute) {
this.mute = mute;
}
public Group mute(Boolean mute) {
this.mute = mute;
return this;
}
/**
* Average utput volume in dB
* minimum: -79
* maximum: 0
*
* @return volDelta
**/
public Integer getVolDelta() {
return volDelta;
}
public void setVolDelta(Integer volDelta) {
this.volDelta = volDelta;
}
public Group volDelta(Integer volDelta) {
this.volDelta = volDelta;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Group {\n");
sb.append(" id: ").append(toIndentedString(id)).append("\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("}");
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 ");
}
}

View File

@ -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.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Reconfiguration of a Group
**/
@Schema(description = "Reconfiguration of a Group ")
public class GroupUpdate {
@Schema
/**
* Friendly name
**/
private String name;
@Schema
@SerializedName("source_id")
/**
* id of the connected source
**/
private Integer sourceId;
@Schema
/**
* Set of zones belonging to a group
**/
private List<Integer> zones;
@Schema
/**
* Set to true if output is all zones muted
**/
private Boolean mute;
@Schema
@SerializedName("vol_delta")
/**
* Average utput volume in dB
**/
private Integer volDelta;
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public GroupUpdate name(String name) {
this.name = name;
return this;
}
/**
* id of the connected source
* minimum: 0
* maximum: 3
*
* @return sourceId
**/
public Integer getSourceId() {
return sourceId;
}
public void setSourceId(Integer sourceId) {
this.sourceId = sourceId;
}
public GroupUpdate sourceId(Integer sourceId) {
this.sourceId = sourceId;
return this;
}
/**
* Set of zones belonging to a group
*
* @return zones
**/
public List<Integer> getZones() {
return zones;
}
public void setZones(List<Integer> zones) {
this.zones = zones;
}
public GroupUpdate zones(List<Integer> zones) {
this.zones = zones;
return this;
}
public GroupUpdate addZonesItem(Integer zonesItem) {
this.zones.add(zonesItem);
return this;
}
/**
* Set to true if output is all zones muted
*
* @return mute
**/
public Boolean getMute() {
return mute;
}
public void setMute(Boolean mute) {
this.mute = mute;
}
public GroupUpdate mute(Boolean mute) {
this.mute = mute;
return this;
}
/**
* Average utput volume in dB
* minimum: -79
* maximum: 0
*
* @return volDelta
**/
public Integer getVolDelta() {
return volDelta;
}
public void setVolDelta(Integer volDelta) {
this.volDelta = volDelta;
}
public GroupUpdate volDelta(Integer volDelta) {
this.volDelta = volDelta;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class GroupUpdate {\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("}");
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 ");
}
}

View File

@ -0,0 +1,204 @@
/**
* 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.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Reconfiguration of a specific Group
**/
@Schema(description = "Reconfiguration of a specific Group ")
public class GroupUpdate2 {
@Schema
/**
* Friendly name
**/
private String name;
@Schema
/**
* id of the connected source
**/
@SerializedName("source_id")
private Integer sourceId = 0;
@Schema
/**
* Set of zones belonging to a group
**/
private List<Integer> zones = null;
@Schema
/**
* Set to true if output is all zones muted
**/
private Boolean mute = true;
@Schema
@SerializedName("vol_delta")
/**
* Average output volume in dB
**/
private Integer volDelta = -79;
@Schema(required = true)
private Integer id;
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public GroupUpdate2 name(String name) {
this.name = name;
return this;
}
/**
* id of the connected source
* minimum: 0
* maximum: 3
*
* @return sourceId
**/
public Integer getSourceId() {
return sourceId;
}
public void setSourceId(Integer sourceId) {
this.sourceId = sourceId;
}
public GroupUpdate2 sourceId(Integer sourceId) {
this.sourceId = sourceId;
return this;
}
/**
* Set of zones belonging to a group
*
* @return zones
**/
public List<Integer> getZones() {
return zones;
}
public void setZones(List<Integer> zones) {
this.zones = zones;
}
public GroupUpdate2 zones(List<Integer> zones) {
this.zones = zones;
return this;
}
public GroupUpdate2 addZonesItem(Integer zonesItem) {
this.zones.add(zonesItem);
return this;
}
/**
* Set to true if output is all zones muted
*
* @return mute
**/
public Boolean getMute() {
return mute;
}
public void setMute(Boolean mute) {
this.mute = mute;
}
public GroupUpdate2 mute(Boolean mute) {
this.mute = mute;
return this;
}
/**
* Average output volume in dB
* minimum: -79
* maximum: 0
*
* @return volDelta
**/
public Integer getVolDelta() {
return volDelta;
}
public void setVolDelta(Integer volDelta) {
this.volDelta = volDelta;
}
public GroupUpdate2 volDelta(Integer volDelta) {
this.volDelta = volDelta;
return this;
}
/**
* Get id
*
* @return id
**/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public GroupUpdate2 id(Integer id) {
this.id = id;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class GroupUpdate2 {\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 ");
}
}

View File

@ -0,0 +1,67 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
public class HTTPValidationError {
@Schema
private List<ValidationError> detail = null;
/**
* Get detail
*
* @return detail
**/
public List<ValidationError> getDetail() {
return detail;
}
public void setDetail(List<ValidationError> detail) {
this.detail = detail;
}
public HTTPValidationError detail(List<ValidationError> detail) {
this.detail = detail;
return this;
}
public HTTPValidationError addDetailItem(ValidationError detailItem) {
this.detail.add(detailItem);
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class HTTPValidationError {\n");
sb.append(" detail: ").append(toIndentedString(detail)).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 ");
}
}

View File

@ -0,0 +1,135 @@
/**
* 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.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Information about the settings used by the controller
**/
@Schema(description = "Information about the settings used by the controller ")
public class Info {
@Schema
@SerializedName("config_file")
private String configFile = "Unknown";
@Schema
private String version = "Unknown";
@Schema
@SerializedName("mock_ctrl")
private Boolean mockCtrl = false;
@Schema
@SerializedName("mock_streams")
private Boolean mockStreams = false;
/**
* Get configFile
*
* @return configFile
**/
public String getConfigFile() {
return configFile;
}
public void setConfigFile(String configFile) {
this.configFile = configFile;
}
public Info configFile(String configFile) {
this.configFile = configFile;
return this;
}
/**
* Get version
*
* @return version
**/
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public Info version(String version) {
this.version = version;
return this;
}
/**
* Get mockCtrl
*
* @return mockCtrl
**/
public Boolean getMockCtrl() {
return mockCtrl;
}
public void setMockCtrl(Boolean mockCtrl) {
this.mockCtrl = mockCtrl;
}
public Info mockCtrl(Boolean mockCtrl) {
this.mockCtrl = mockCtrl;
return this;
}
/**
* Get mockStreams
*
* @return mockStreams
**/
public Boolean getMockStreams() {
return mockStreams;
}
public void setMockStreams(Boolean mockStreams) {
this.mockStreams = mockStreams;
}
public Info mockStreams(Boolean mockStreams) {
this.mockStreams = mockStreams;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Info {\n");
sb.append(" configFile: ").append(toIndentedString(configFile)).append("\n");
sb.append(" version: ").append(toIndentedString(version)).append("\n");
sb.append(" mockCtrl: ").append(toIndentedString(mockCtrl)).append("\n");
sb.append(" mockStreams: ").append(toIndentedString(mockStreams)).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 ");
}
}

View File

@ -0,0 +1,169 @@
/**
* 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.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* A partial controller configuration the can be loaded on demand. In addition to most of the configuration found in
* Status, this can contain commands as well that configure the state of different streaming services.
**/
@Schema(description = "A partial controller configuration the can be loaded on demand. In addition to most of the configuration found in Status, this can contain commands as well that configure the state of different streaming services.")
public class Preset {
@Schema
/**
* Unique identifier
**/
private Integer id;
@Schema(required = true)
/**
* Friendly name
**/
private String name;
@Schema(required = true)
private PresetState state;
@Schema
private List<Command> commands = null;
@Schema
@SerializedName("last_used")
private Integer lastUsed;
/**
* Unique identifier
*
* @return id
**/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Preset id(Integer id) {
this.id = id;
return this;
}
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Preset name(String name) {
this.name = name;
return this;
}
/**
* Get state
*
* @return state
**/
public PresetState getState() {
return state;
}
public void setState(PresetState state) {
this.state = state;
}
public Preset state(PresetState state) {
this.state = state;
return this;
}
/**
* Get commands
*
* @return commands
**/
public List<Command> getCommands() {
return commands;
}
public void setCommands(List<Command> commands) {
this.commands = commands;
}
public Preset commands(List<Command> commands) {
this.commands = commands;
return this;
}
public Preset addCommandsItem(Command commandsItem) {
this.commands.add(commandsItem);
return this;
}
/**
* Get lastUsed
*
* @return lastUsed
**/
public Integer getLastUsed() {
return lastUsed;
}
public void setLastUsed(Integer lastUsed) {
this.lastUsed = lastUsed;
}
public Preset lastUsed(Integer lastUsed) {
this.lastUsed = lastUsed;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Preset {\n");
sb.append(" id: ").append(toIndentedString(id)).append("\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" state: ").append(toIndentedString(state)).append("\n");
sb.append(" commands: ").append(toIndentedString(commands)).append("\n");
sb.append(" lastUsed: ").append(toIndentedString(lastUsed)).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 ");
}
}

View File

@ -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 io.swagger.v3.oas.annotations.media.Schema;
/**
* A set of partial configuration changes to make to sources, zones, and groups
**/
@Schema(description = "A set of partial configuration changes to make to sources, zones, and groups ")
public class PresetState {
@Schema
private List<SourceUpdate2> sources = null;
@Schema
private List<ZoneUpdate2> zones = null;
@Schema
private List<GroupUpdate2> groups = null;
/**
* Get sources
*
* @return sources
**/
public List<SourceUpdate2> getSources() {
return sources;
}
public void setSources(List<SourceUpdate2> sources) {
this.sources = sources;
}
public PresetState sources(List<SourceUpdate2> sources) {
this.sources = sources;
return this;
}
public PresetState addSourcesItem(SourceUpdate2 sourcesItem) {
this.sources.add(sourcesItem);
return this;
}
/**
* Get zones
*
* @return zones
**/
public List<ZoneUpdate2> getZones() {
return zones;
}
public void setZones(List<ZoneUpdate2> zones) {
this.zones = zones;
}
public PresetState zones(List<ZoneUpdate2> zones) {
this.zones = zones;
return this;
}
public PresetState addZonesItem(ZoneUpdate2 zonesItem) {
this.zones.add(zonesItem);
return this;
}
/**
* Get groups
*
* @return groups
**/
public List<GroupUpdate2> getGroups() {
return groups;
}
public void setGroups(List<GroupUpdate2> groups) {
this.groups = groups;
}
public PresetState groups(List<GroupUpdate2> groups) {
this.groups = groups;
return this;
}
public PresetState addGroupsItem(GroupUpdate2 groupsItem) {
this.groups.add(groupsItem);
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class PresetState {\n");
sb.append(" sources: ").append(toIndentedString(sources)).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 ");
}
}

View File

@ -0,0 +1,119 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
/**
* Changes to a current preset The contents of state and commands will be completely replaced if populated. Merging old
* and new updates seems too complicated and error prone.
**/
@Schema(description = "Changes to a current preset The contents of state and commands will be completely replaced if populated. Merging old and new updates seems too complicated and error prone.")
public class PresetUpdate {
@Schema
/**
* Friendly name
**/
private String name;
@Schema
private PresetState state;
@Schema
private List<Command> commands = null;
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public PresetUpdate name(String name) {
this.name = name;
return this;
}
/**
* Get state
*
* @return state
**/
public PresetState getState() {
return state;
}
public void setState(PresetState state) {
this.state = state;
}
public PresetUpdate state(PresetState state) {
this.state = state;
return this;
}
/**
* Get commands
*
* @return commands
**/
public List<Command> getCommands() {
return commands;
}
public void setCommands(List<Command> commands) {
this.commands = commands;
}
public PresetUpdate commands(List<Command> commands) {
this.commands = commands;
return this;
}
public PresetUpdate addCommandsItem(Command commandsItem) {
this.commands.add(commandsItem);
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class PresetUpdate {\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" state: ").append(toIndentedString(state)).append("\n");
sb.append(" commands: ").append(toIndentedString(commands)).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 ");
}
}

View File

@ -0,0 +1,121 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
/**
* An audio source
**/
@Schema(description = "An audio source ")
public class Source {
@Schema
/**
* Unique identifier
**/
private Integer id;
@Schema(required = true)
/**
* Friendly name
**/
private String name;
@Schema
/**
* Connected audio source * Digital Stream ('stream=SID') where SID is the ID of the connected stream * Analog RCA
* Input ('local') connects to the RCA inputs associated * Nothing ('') behind the scenes this is muxed to a digital
* output
**/
private String input = "";
/**
* Unique identifier
*
* @return id
**/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Source id(Integer id) {
this.id = id;
return this;
}
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Source name(String name) {
this.name = name;
return this;
}
/**
* Connected audio source * Digital Stream (&#39;stream&#x3D;SID&#39;) where SID is the ID of the connected stream *
* Analog RCA Input (&#39;local&#39;) connects to the RCA inputs associated * Nothing (&#39;&#39;) behind the scenes
* this is muxed to a digital output
*
* @return input
**/
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public Source input(String input) {
this.input = input;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Source {\n");
sb.append(" id: ").append(toIndentedString(id)).append("\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" input: ").append(toIndentedString(input)).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 ");
}
}

View File

@ -0,0 +1,89 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
/**
* Partial reconfiguration of an audio Source
**/
@Schema(description = "Partial reconfiguration of an audio Source ")
public class SourceUpdate {
@Schema
/**
* Friendly name
**/
private String name;
@Schema
private String input;
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SourceUpdate name(String name) {
this.name = name;
return this;
}
/**
* Get input
*
* @return input
**/
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public SourceUpdate input(String input) {
this.input = input;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class SourceUpdate {\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" input: ").append(toIndentedString(input)).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 ");
}
}

View File

@ -0,0 +1,113 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
/**
* Partial reconfiguration of a specific audio Source
**/
@Schema(description = "Partial reconfiguration of a specific audio Source ")
public class SourceUpdate2 {
@Schema
/**
* Friendly name
**/
private String name;
@Schema
private String input;
@Schema(required = true)
private Integer id;
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SourceUpdate2 name(String name) {
this.name = name;
return this;
}
/**
* Get input
*
* @return input
**/
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public SourceUpdate2 input(String input) {
this.input = input;
return this;
}
/**
* Get id
* minimum: 0
* maximum: 4
*
* @return id
**/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public SourceUpdate2 id(Integer id) {
this.id = id;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class SourceUpdate2 {\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 ");
}
}

View File

@ -0,0 +1,201 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
/**
* Full Controller Configuration and Status
**/
@Schema(description = "Full Controller Configuration and Status ")
public class Status {
@Schema
private List<Source> sources = null;
@Schema
private List<Zone> zones = null;
@Schema
private List<Group> groups = null;
@Schema
private List<Stream> streams = null;
@Schema
private List<Preset> presets = null;
@Schema
private Info info;
/**
* Get sources
*
* @return sources
**/
public List<Source> getSources() {
return sources;
}
public void setSources(List<Source> sources) {
this.sources = sources;
}
public Status sources(List<Source> sources) {
this.sources = sources;
return this;
}
public Status addSourcesItem(Source sourcesItem) {
this.sources.add(sourcesItem);
return this;
}
/**
* Get zones
*
* @return zones
**/
public List<Zone> getZones() {
return zones;
}
public void setZones(List<Zone> zones) {
this.zones = zones;
}
public Status zones(List<Zone> zones) {
this.zones = zones;
return this;
}
public Status addZonesItem(Zone zonesItem) {
this.zones.add(zonesItem);
return this;
}
/**
* Get groups
*
* @return groups
**/
public List<Group> getGroups() {
return groups;
}
public void setGroups(List<Group> groups) {
this.groups = groups;
}
public Status groups(List<Group> groups) {
this.groups = groups;
return this;
}
public Status addGroupsItem(Group groupsItem) {
this.groups.add(groupsItem);
return this;
}
/**
* Get streams
*
* @return streams
**/
public List<Stream> getStreams() {
return streams;
}
public void setStreams(List<Stream> streams) {
this.streams = streams;
}
public Status streams(List<Stream> streams) {
this.streams = streams;
return this;
}
public Status addStreamsItem(Stream streamsItem) {
this.streams.add(streamsItem);
return this;
}
/**
* Get presets
*
* @return presets
**/
public List<Preset> getPresets() {
return presets;
}
public void setPresets(List<Preset> presets) {
this.presets = presets;
}
public Status presets(List<Preset> presets) {
this.presets = presets;
return this;
}
public Status addPresetsItem(Preset presetsItem) {
this.presets.add(presetsItem);
return this;
}
/**
* Get info
*
* @return info
**/
public Info getInfo() {
return info;
}
public void setInfo(Info info) {
this.info = info;
}
public Status info(Info info) {
this.info = info;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Status {\n");
sb.append(" sources: ").append(toIndentedString(sources)).append("\n");
sb.append(" zones: ").append(toIndentedString(zones)).append("\n");
sb.append(" groups: ").append(toIndentedString(groups)).append("\n");
sb.append(" streams: ").append(toIndentedString(streams)).append("\n");
sb.append(" presets: ").append(toIndentedString(presets)).append("\n");
sb.append(" info: ").append(toIndentedString(info)).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 ");
}
}

View File

@ -0,0 +1,292 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
/**
* Digital stream such as Pandora, Airplay or Spotify
**/
@Schema(description = "Digital stream such as Pandora, Airplay or Spotify ")
public class Stream {
@Schema
/**
* Unique identifier
**/
private Integer id;
@Schema(required = true)
/**
* Friendly name
**/
private String name;
@Schema(required = true)
/**
* stream type * pandora * shairport * dlna * internetradio * spotify
**/
private String type;
@Schema
/**
* User login
**/
private String user;
@Schema
/**
* Password
**/
private String password;
@Schema
/**
* Radio station identifier
**/
private String station;
@Schema
/**
* Stream url, used for internetradio
**/
private String url;
@Schema
/**
* Icon/Logo url, used for internetradio
**/
private String logo;
@Schema
/**
* Additional info about the current audio playing from the stream (generated during playback
**/
private Object info;
@Schema
/**
* State of the stream
**/
private String status;
/**
* Unique identifier
*
* @return id
**/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Stream id(Integer id) {
this.id = id;
return this;
}
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Stream name(String name) {
this.name = name;
return this;
}
/**
* stream type * pandora * shairport * dlna * internetradio * spotify
*
* @return type
**/
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Stream type(String type) {
this.type = type;
return this;
}
/**
* User login
*
* @return user
**/
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public Stream user(String user) {
this.user = user;
return this;
}
/**
* Password
*
* @return password
**/
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Stream password(String password) {
this.password = password;
return this;
}
/**
* Radio station identifier
*
* @return station
**/
public String getStation() {
return station;
}
public void setStation(String station) {
this.station = station;
}
public Stream station(String station) {
this.station = station;
return this;
}
/**
* Stream url, used for internetradio
*
* @return url
**/
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Stream url(String url) {
this.url = url;
return this;
}
/**
* Icon/Logo url, used for internetradio
*
* @return logo
**/
public String getLogo() {
return logo;
}
public void setLogo(String logo) {
this.logo = logo;
}
public Stream logo(String logo) {
this.logo = logo;
return this;
}
/**
* Additional info about the current audio playing from the stream (generated during playback
*
* @return info
**/
public Object getInfo() {
return info;
}
public void setInfo(Object info) {
this.info = info;
}
public Stream info(Object info) {
this.info = info;
return this;
}
/**
* State of the stream
*
* @return status
**/
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Stream status(String status) {
this.status = status;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Stream {\n");
sb.append(" id: ").append(toIndentedString(id)).append("\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" type: ").append(toIndentedString(type)).append("\n");
sb.append(" user: ").append(toIndentedString(user)).append("\n");
sb.append(" password: ").append(toIndentedString(password)).append("\n");
sb.append(" station: ").append(toIndentedString(station)).append("\n");
sb.append(" url: ").append(toIndentedString(url)).append("\n");
sb.append(" logo: ").append(toIndentedString(logo)).append("\n");
sb.append(" info: ").append(toIndentedString(info)).append("\n");
sb.append(" status: ").append(toIndentedString(status)).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 ");
}
}

View File

@ -0,0 +1,55 @@
/**
* 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;
/**
* An enumeration.
*/
public enum StreamCommand {
PLAY("play"),
PAUSE("pause"),
NEXT("next"),
STOP("stop"),
LIKE("like"),
BAN("ban"),
SHELVE("shelve");
private String value;
StreamCommand(String value) {
this.value = value;
}
@Override
public String toString() {
return String.valueOf(value);
}
public static StreamCommand fromValue(String value) {
for (StreamCommand b : StreamCommand.values()) {
if (b.value.equals(value)) {
return b;
}
}
throw new IllegalArgumentException("Unexpected value '" + value + "'");
}
}

View File

@ -0,0 +1,177 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
/**
* Reconfiguration of a Stream
**/
@Schema(description = "Reconfiguration of a Stream ")
public class StreamUpdate {
@Schema
/**
* Friendly name
**/
private String name;
@Schema
private String user;
@Schema
private String password;
@Schema
private String station;
@Schema
private String url;
@Schema
private String logo;
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public StreamUpdate name(String name) {
this.name = name;
return this;
}
/**
* Get user
*
* @return user
**/
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public StreamUpdate user(String user) {
this.user = user;
return this;
}
/**
* Get password
*
* @return password
**/
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public StreamUpdate password(String password) {
this.password = password;
return this;
}
/**
* Get station
*
* @return station
**/
public String getStation() {
return station;
}
public void setStation(String station) {
this.station = station;
}
public StreamUpdate station(String station) {
this.station = station;
return this;
}
/**
* Get url
*
* @return url
**/
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public StreamUpdate url(String url) {
this.url = url;
return this;
}
/**
* Get logo
*
* @return logo
**/
public String getLogo() {
return logo;
}
public void setLogo(String logo) {
this.logo = logo;
}
public StreamUpdate logo(String logo) {
this.logo = logo;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class StreamUpdate {\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" user: ").append(toIndentedString(user)).append("\n");
sb.append(" password: ").append(toIndentedString(password)).append("\n");
sb.append(" station: ").append(toIndentedString(station)).append("\n");
sb.append(" url: ").append(toIndentedString(url)).append("\n");
sb.append(" logo: ").append(toIndentedString(logo)).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 ");
}
}

View File

@ -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 java.util.ArrayList;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
public class ValidationError {
@Schema(required = true)
private List<String> loc = new ArrayList<String>();
@Schema(required = true)
private String msg;
@Schema(required = true)
private String type;
/**
* Get loc
*
* @return loc
**/
public List<String> getLoc() {
return loc;
}
public void setLoc(List<String> loc) {
this.loc = loc;
}
public ValidationError loc(List<String> loc) {
this.loc = loc;
return this;
}
public ValidationError addLocItem(String locItem) {
this.loc.add(locItem);
return this;
}
/**
* Get msg
*
* @return msg
**/
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public ValidationError msg(String msg) {
this.msg = msg;
return this;
}
/**
* Get type
*
* @return type
**/
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public ValidationError type(String type) {
this.type = type;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ValidationError {\n");
sb.append(" loc: ").append(toIndentedString(loc)).append("\n");
sb.append(" msg: ").append(toIndentedString(msg)).append("\n");
sb.append(" type: ").append(toIndentedString(type)).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 ");
}
}

View File

@ -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 com.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Audio output to a stereo pair of speakers, typically belonging to a room
**/
@Schema(description = "Audio output to a stereo pair of speakers, typically belonging to a room ")
public class Zone {
@Schema
/**
* Unique identifier
**/
private Integer id;
@Schema(required = true)
/**
* Friendly name
**/
private String name;
@Schema
@SerializedName("source_id")
/**
* id of the connected source
**/
private Integer sourceId = 0;
@Schema
/**
* Set to true if output is muted
**/
private Boolean mute = true;
@Schema
/**
* Output volume in dB
**/
private Integer vol = -79;
@Schema
/**
* Set to true if not connected to a speaker
**/
private Boolean disabled = false;
/**
* Unique identifier
*
* @return id
**/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Zone id(Integer id) {
this.id = id;
return this;
}
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Zone name(String name) {
this.name = name;
return this;
}
/**
* id of the connected source
* minimum: 0
* maximum: 3
*
* @return sourceId
**/
public Integer getSourceId() {
return sourceId;
}
public void setSourceId(Integer sourceId) {
this.sourceId = sourceId;
}
public Zone sourceId(Integer sourceId) {
this.sourceId = sourceId;
return this;
}
/**
* Set to true if output is muted
*
* @return mute
**/
public Boolean getMute() {
return mute;
}
public void setMute(Boolean mute) {
this.mute = mute;
}
public Zone mute(Boolean mute) {
this.mute = mute;
return this;
}
/**
* Output volume in dB
* minimum: -79
* maximum: 0
*
* @return vol
**/
public Integer getVol() {
return vol;
}
public void setVol(Integer vol) {
this.vol = vol;
}
public Zone vol(Integer vol) {
this.vol = vol;
return this;
}
/**
* Set to true if not connected to a speaker
*
* @return disabled
**/
public Boolean getDisabled() {
return disabled;
}
public void setDisabled(Boolean disabled) {
this.disabled = disabled;
}
public Zone disabled(Boolean disabled) {
this.disabled = disabled;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Zone {\n");
sb.append(" id: ").append(toIndentedString(id)).append("\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("}");
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 ");
}
}

View File

@ -0,0 +1,174 @@
/**
* 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.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Reconfiguration of a Zone
**/
@Schema(description = "Reconfiguration of a Zone ")
public class ZoneUpdate {
@Schema
/**
* Friendly name
**/
private String name;
@Schema
@SerializedName("source_id")
/**
* id of the connected source
**/
private Integer sourceId;
@Schema
/**
* Set to true if output is muted
**/
private Boolean mute;
@Schema
/**
* Output volume in dB
**/
private Integer vol;
@Schema
/**
* Set to true if not connected to a speaker
**/
private Boolean disabled;
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ZoneUpdate name(String name) {
this.name = name;
return this;
}
/**
* id of the connected source
* minimum: 0
* maximum: 3
*
* @return sourceId
**/
public Integer getSourceId() {
return sourceId;
}
public void setSourceId(Integer sourceId) {
this.sourceId = sourceId;
}
public ZoneUpdate sourceId(Integer sourceId) {
this.sourceId = sourceId;
return this;
}
/**
* Set to true if output is muted
*
* @return mute
**/
public Boolean getMute() {
return mute;
}
public void setMute(Boolean mute) {
this.mute = mute;
}
public ZoneUpdate mute(Boolean mute) {
this.mute = mute;
return this;
}
/**
* Output volume in dB
* minimum: -79
* maximum: 0
*
* @return vol
**/
public Integer getVol() {
return vol;
}
public void setVol(Integer vol) {
this.vol = vol;
}
public ZoneUpdate vol(Integer vol) {
this.vol = vol;
return this;
}
/**
* Set to true if not connected to a speaker
*
* @return disabled
**/
public Boolean getDisabled() {
return disabled;
}
public void setDisabled(Boolean disabled) {
this.disabled = disabled;
}
public ZoneUpdate disabled(Boolean disabled) {
this.disabled = disabled;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ZoneUpdate {\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("}");
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 ");
}
}

View File

@ -0,0 +1,198 @@
/**
* 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.google.gson.annotations.SerializedName;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Reconfiguration of a specific Zone
**/
@Schema(description = "Reconfiguration of a specific Zone ")
public class ZoneUpdate2 {
@Schema
/**
* Friendly name
**/
private String name;
@Schema
@SerializedName("source_id")
/**
* id of the connected source
**/
private Integer sourceId = 0;
@Schema
/**
* Set to true if output is muted
**/
private Boolean mute = true;
@Schema
/**
* Output volume in dB
**/
private Integer vol = -79;
@Schema
/**
* Set to true if not connected to a speaker
**/
private Boolean disabled = false;
@Schema(required = true)
private Integer id;
/**
* Friendly name
*
* @return name
**/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ZoneUpdate2 name(String name) {
this.name = name;
return this;
}
/**
* id of the connected source
* minimum: 0
* maximum: 3
*
* @return sourceId
**/
public Integer getSourceId() {
return sourceId;
}
public void setSourceId(Integer sourceId) {
this.sourceId = sourceId;
}
public ZoneUpdate2 sourceId(Integer sourceId) {
this.sourceId = sourceId;
return this;
}
/**
* Set to true if output is muted
*
* @return mute
**/
public Boolean getMute() {
return mute;
}
public void setMute(Boolean mute) {
this.mute = mute;
}
public ZoneUpdate2 mute(Boolean mute) {
this.mute = mute;
return this;
}
/**
* Output volume in dB
* minimum: -79
* maximum: 0
*
* @return vol
**/
public Integer getVol() {
return vol;
}
public void setVol(Integer vol) {
this.vol = vol;
}
public ZoneUpdate2 vol(Integer vol) {
this.vol = vol;
return this;
}
/**
* Set to true if not connected to a speaker
*
* @return disabled
**/
public Boolean getDisabled() {
return disabled;
}
public void setDisabled(Boolean disabled) {
this.disabled = disabled;
}
public ZoneUpdate2 disabled(Boolean disabled) {
this.disabled = disabled;
return this;
}
/**
* Get id
* minimum: 0
* maximum: 35
*
* @return id
**/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public ZoneUpdate2 id(Integer id) {
this.id = id;
return this;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ZoneUpdate2 {\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 ");
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.amplipi-${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-amplipi" description="AmpliPi Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.amplipi/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,44 @@
/**
* 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link AmpliPiBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
public class AmpliPiBindingConstants {
private static final String BINDING_ID = "amplipi";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_CONTROLLER = new ThingTypeUID(BINDING_ID, "controller");
public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
public static final ThingTypeUID THING_TYPE_GROUP = new ThingTypeUID(BINDING_ID, "group");
// List of all Channel ids
public static final String CHANNEL_PRESET = "preset";
public static final String CHANNEL_INPUT = "input";
public static final String CHANNEL_VOLUME = "volume";
public static final String CHANNEL_MUTE = "mute";
public static final String CHANNEL_SOURCE = "source";
// list of configuration parameters
public static final String CFG_PARAM_HOSTNAME = "hostname";
public static final String CFG_PARAM_ID = "id";
}

View File

@ -0,0 +1,27 @@
/**
* 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;
/**
* The {@link AmpliPiConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Kai Kreuzer - Initial contribution
*/
public class AmpliPiConfiguration {
/**
* Sample configuration parameters. Replace with your own.
*/
public String hostname;
public int refreshInterval;
}

View File

@ -0,0 +1,149 @@
/**
* 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;
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;
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.model.Group;
import org.openhab.binding.amplipi.internal.model.GroupUpdate;
import org.openhab.binding.amplipi.internal.model.Status;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Bridge;
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.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link AmpliPiGroupHandler} is responsible for handling commands, which are
* sent to one of the AmpliPi Groups.
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
public class AmpliPiGroupHandler extends BaseThingHandler implements AmpliPiStatusChangeListener {
private final Logger logger = LoggerFactory.getLogger(AmpliPiGroupHandler.class);
private final HttpClient httpClient;
private final Gson gson;
private @Nullable AmpliPiHandler bridgeHandler;
public AmpliPiGroupHandler(Thing thing, HttpClient httpClient) {
super(thing);
this.httpClient = httpClient;
this.gson = new Gson();
}
private int getId(Thing thing) {
return Integer.valueOf(thing.getConfiguration().get(AmpliPiBindingConstants.CFG_PARAM_ID).toString());
}
@Override
public void initialize() {
Bridge bridge = getBridge();
if (bridge != null) {
bridgeHandler = (AmpliPiHandler) bridge.getHandler();
if (bridgeHandler != null) {
bridgeHandler.addStatusChangeListener(this);
} else {
throw new IllegalStateException("Bridge handler must not be null here!");
}
if (bridge.getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
} else {
throw new IllegalStateException("Bridge must not be null here!");
}
}
@Override
public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) {
if (command == RefreshType.REFRESH) {
// do nothing - we just wait for the next automatic refresh
return;
}
GroupUpdate update = new GroupUpdate();
switch (channelUID.getId()) {
case AmpliPiBindingConstants.CHANNEL_MUTE:
if (command instanceof OnOffType) {
update.setMute(command == OnOffType.ON);
}
break;
case AmpliPiBindingConstants.CHANNEL_VOLUME:
if (command instanceof PercentType) {
update.setVolDelta(AmpliPiUtils.percentTypeToVolume((PercentType) command));
}
break;
case AmpliPiBindingConstants.CHANNEL_SOURCE:
if (command instanceof DecimalType) {
update.setSourceId(((DecimalType) command).intValue());
}
break;
}
if (bridgeHandler != null) {
String url = bridgeHandler.getUrl() + "/api/groups/" + getId(thing);
;
StringContentProvider contentProvider = new StringContentProvider(gson.toJson(update));
try {
ContentResponse response = httpClient.newRequest(url).method(HttpMethod.PATCH)
.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 {
updateStatus(ThingStatus.ONLINE);
}
} catch (InterruptedException | TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"AmpliPi request failed: " + e.getMessage());
}
}
}
@Override
public void receive(@NonNull Status status) {
int id = getId(thing);
Optional<Group> group = status.getGroups().stream().filter(z -> z.getId().equals(id)).findFirst();
if (group.isPresent()) {
Boolean mute = group.get().getMute();
Integer volume = group.get().getVolDelta();
Integer source = group.get().getSourceId();
updateState(AmpliPiBindingConstants.CHANNEL_MUTE, mute ? OnOffType.ON : OnOffType.OFF);
updateState(AmpliPiBindingConstants.CHANNEL_VOLUME, AmpliPiUtils.volumeToPercentType(volume));
updateState(AmpliPiBindingConstants.CHANNEL_SOURCE, new DecimalType(source));
}
}
}

View File

@ -0,0 +1,215 @@
/**
* 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;
import static org.openhab.binding.amplipi.internal.AmpliPiBindingConstants.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
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.discovery.AmpliPiZoneAndGroupDiscoveryService;
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.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
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.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link AmpliPiHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
public class AmpliPiHandler extends BaseBridgeHandler {
private static final int REQUEST_TIMEOUT = 5000;
private final Logger logger = LoggerFactory.getLogger(AmpliPiHandler.class);
private final HttpClient httpClient;
private final Gson gson;
private String url = "http://amplipi";
private List<Preset> presets = List.of();
private List<Stream> streams = List.of();
private List<AmpliPiStatusChangeListener> changeListeners = new ArrayList<>();
private @Nullable ScheduledFuture<?> refreshJob;
public AmpliPiHandler(Thing thing, HttpClient httpClient) {
super((Bridge) thing);
this.httpClient = httpClient;
this.gson = new Gson();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
AmpliPiConfiguration config = getConfigAs(AmpliPiConfiguration.class);
url = "http://" + config.hostname;
if (CHANNEL_PRESET.equals(channelUID.getId())) {
if (command instanceof RefreshType) {
updateState(channelUID, UnDefType.NULL);
} else if (command instanceof DecimalType) {
DecimalType preset = (DecimalType) command;
try {
ContentResponse response = this.httpClient
.newRequest(url + "/api/presets/" + preset.intValue() + "/load").method(HttpMethod.POST)
.timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).send();
if (response.getStatus() != HttpStatus.OK_200) {
logger.error("AmpliPi API returned HTTP status {}.", response.getStatus());
logger.debug("Content: {}", response.getContentAsString());
}
} catch (InterruptedException | TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"AmpliPi request failed: " + e.getMessage());
}
}
} else if (channelUID.getId().startsWith(CHANNEL_INPUT)) {
if (command instanceof StringType) {
StringType input = (StringType) command;
int source = Integer.valueOf(channelUID.getId().substring(CHANNEL_INPUT.length())) - 1;
SourceUpdate update = new SourceUpdate();
update.setInput(input.toString());
try {
StringContentProvider contentProvider = new StringContentProvider(gson.toJson(update));
ContentResponse response = this.httpClient.newRequest(url + "/api/sources/" + source)
.method(HttpMethod.PATCH).content(contentProvider, "application/json")
.timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).send();
if (response.getStatus() != HttpStatus.OK_200) {
logger.error("AmpliPi API returned HTTP status {}.", response.getStatus());
logger.debug("Content: {}", response.getContentAsString());
}
} catch (InterruptedException | TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"AmpliPi request failed: " + e.getMessage());
}
}
}
}
@Override
public void initialize() {
AmpliPiConfiguration config = getConfigAs(AmpliPiConfiguration.class);
url = "http://" + config.hostname;
updateStatus(ThingStatus.UNKNOWN);
refreshJob = scheduler.scheduleWithFixedDelay(() -> {
try {
ContentResponse response = this.httpClient.newRequest(url + "/api")
.timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).send();
if (response.getStatus() == HttpStatus.OK_200) {
Status currentStatus = this.gson.fromJson(response.getContentAsString(), Status.class);
if (currentStatus != null) {
updateStatus(ThingStatus.ONLINE);
setProperties(currentStatus);
setInputs(currentStatus);
presets = currentStatus.getPresets();
streams = currentStatus.getStreams();
changeListeners.forEach(l -> l.receive(currentStatus));
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"No valid response from AmpliPi API.");
logger.debug("Received response: {}", response.getContentAsString());
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"AmpliPi API returned HTTP status " + response.getStatus() + ".");
}
} catch (InterruptedException | TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"AmpliPi request failed: " + e.getMessage());
} catch (Exception e) {
logger.error("Unexpected error occurred: {}", e.getMessage());
}
}, 0, config.refreshInterval, TimeUnit.SECONDS);
}
private void setProperties(Status currentStatus) {
String version = currentStatus.getInfo().getVersion();
Map<String, String> props = editProperties();
props.put(Thing.PROPERTY_FIRMWARE_VERSION, version);
updateProperties(props);
}
private void setInputs(Status currentStatus) {
currentStatus.getSources().forEach(source -> {
updateState(CHANNEL_INPUT + (source.getId() + 1), new StringType(source.getInput()));
});
}
@Override
public void dispose() {
if (refreshJob != null) {
refreshJob.cancel(true);
refreshJob = null;
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(PresetCommandOptionProvider.class, InputStateOptionProvider.class,
AmpliPiZoneAndGroupDiscoveryService.class);
}
public List<Preset> getPresets() {
return presets;
}
public List<Stream> getStreams() {
return streams;
}
public String getUrl() {
return url;
}
public void addStatusChangeListener(AmpliPiStatusChangeListener listener) {
changeListeners.add(listener);
}
public void removeStatusChangeListener(AmpliPiStatusChangeListener listener) {
changeListeners.remove(listener);
}
}

View File

@ -0,0 +1,73 @@
/**
* 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;
import static org.openhab.binding.amplipi.internal.AmpliPiBindingConstants.*;
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.io.net.http.HttpClientFactory;
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 AmpliPiHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.amplipi", service = ThingHandlerFactory.class)
public class AmpliPiHandlerFactory extends BaseThingHandlerFactory {
private HttpClient httpClient;
@Activate
public AmpliPiHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
}
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_CONTROLLER, THING_TYPE_ZONE,
THING_TYPE_GROUP);
@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 (THING_TYPE_CONTROLLER.equals(thingTypeUID)) {
return new AmpliPiHandler(thing, httpClient);
}
if (THING_TYPE_ZONE.equals(thingTypeUID)) {
return new AmpliPiZoneHandler(thing, httpClient);
}
if (THING_TYPE_GROUP.equals(thingTypeUID)) {
return new AmpliPiGroupHandler(thing, httpClient);
}
return null;
}
}

View File

@ -0,0 +1,33 @@
/**
* 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.amplipi.internal.model.Status;
/**
* This interface is implemented by classes that want to register for any updates from the AmpliPi system.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@NonNullByDefault
public interface AmpliPiStatusChangeListener {
/**
* This method is called whenever a status update occurs.
*
* @param status The current status of the AmpliPi
*/
public void receive(Status status);
}

View File

@ -0,0 +1,48 @@
/**
* 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.PercentType;
/**
* This class has some commonly used static helper functions.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@NonNullByDefault
public class AmpliPiUtils {
/**
* Converts a volume from AmpliPi to an openHAB PercentType
*
* @param volume volume from AmpliPi
* @return according PercentType for openHAB
*/
public static PercentType volumeToPercentType(Integer volume) {
// AmpliPi volumes range from -79..0
return new PercentType((volume + 79) * 100 / 79);
}
/**
* Converts a volume as PercentType from openHAB to an integer for AmpliPi
*
* @param volume volume as PercentType
* @return according volume as int for AmpliPi
*/
public static int percentTypeToVolume(PercentType volume) {
// AmpliPi volumes range from -79..0
return (volume.intValue() * 79 / 100) - 79;
}
}

View File

@ -0,0 +1,148 @@
/**
* 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;
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;
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.model.Status;
import org.openhab.binding.amplipi.internal.model.Zone;
import org.openhab.binding.amplipi.internal.model.ZoneUpdate;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Bridge;
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.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link AmpliPiGroupHandler} is responsible for handling commands, which are
* sent to one of the AmpliPi Groups.
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
public class AmpliPiZoneHandler extends BaseThingHandler implements AmpliPiStatusChangeListener {
private final Logger logger = LoggerFactory.getLogger(AmpliPiZoneHandler.class);
private final HttpClient httpClient;
private final Gson gson;
private @Nullable AmpliPiHandler bridgeHandler;
public AmpliPiZoneHandler(Thing thing, HttpClient httpClient) {
super(thing);
this.httpClient = httpClient;
this.gson = new Gson();
}
@Override
public void initialize() {
Bridge bridge = getBridge();
if (bridge != null) {
bridgeHandler = (AmpliPiHandler) bridge.getHandler();
if (bridgeHandler != null) {
bridgeHandler.addStatusChangeListener(this);
} else {
throw new IllegalStateException("Bridge handler must not be null here!");
}
if (bridge.getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
} else {
throw new IllegalStateException("Bridge must not be null here!");
}
}
private int getId(Thing thing) {
return Integer.valueOf(thing.getConfiguration().get(AmpliPiBindingConstants.CFG_PARAM_ID).toString());
}
@Override
public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) {
if (command == RefreshType.REFRESH) {
// do nothing - we just wait for the next automatic refresh
return;
}
ZoneUpdate update = new ZoneUpdate();
switch (channelUID.getId()) {
case AmpliPiBindingConstants.CHANNEL_MUTE:
if (command instanceof OnOffType) {
update.setMute(command == OnOffType.ON);
}
break;
case AmpliPiBindingConstants.CHANNEL_VOLUME:
if (command instanceof PercentType) {
update.setVol(AmpliPiUtils.percentTypeToVolume((PercentType) command));
}
break;
case AmpliPiBindingConstants.CHANNEL_SOURCE:
if (command instanceof DecimalType) {
update.setSourceId(((DecimalType) command).intValue());
}
break;
}
if (bridgeHandler != null) {
String url = bridgeHandler.getUrl() + "/api/zones/" + getId(thing);
StringContentProvider contentProvider = new StringContentProvider(gson.toJson(update));
try {
ContentResponse response = httpClient.newRequest(url).method(HttpMethod.PATCH)
.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 {
updateStatus(ThingStatus.ONLINE);
}
} catch (InterruptedException | TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"AmpliPi request failed: " + e.getMessage());
}
}
}
@Override
public void receive(@NonNull Status status) {
int id = getId(thing);
Optional<Zone> zone = status.getZones().stream().filter(z -> z.getId().equals(id)).findFirst();
if (zone.isPresent()) {
Boolean mute = zone.get().getMute();
Integer volume = zone.get().getVol();
Integer source = zone.get().getSourceId();
updateState(AmpliPiBindingConstants.CHANNEL_MUTE, mute ? OnOffType.ON : OnOffType.OFF);
updateState(AmpliPiBindingConstants.CHANNEL_VOLUME, AmpliPiUtils.volumeToPercentType(volume));
updateState(AmpliPiBindingConstants.CHANNEL_SOURCE, new DecimalType(source));
}
}
}

View File

@ -0,0 +1,74 @@
/**
* 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;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.amplipi.internal.model.Stream;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.StateOption;
/**
* This class provides the list of valid inputs for the input channel of a source.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@NonNullByDefault
public class InputStateOptionProvider extends BaseDynamicStateDescriptionProvider implements ThingHandlerService {
private @Nullable AmpliPiHandler handler;
@Override
public void setThingHandler(ThingHandler handler) {
this.handler = (AmpliPiHandler) handler;
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
@Override
public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
@Nullable Locale locale) {
ChannelTypeUID typeUID = channel.getChannelTypeUID();
List<StateOption> options = new ArrayList<>();
options.add(new StateOption("local", "RCA"));
if (typeUID != null && AmpliPiBindingConstants.CHANNEL_INPUT.equals(typeUID.getId()) && handler != null) {
List<Stream> streams = handler.getStreams();
for (Stream stream : streams) {
options.add(new StateOption("stream=" + stream.getId(), getLabel(stream)));
}
setStateOptions(channel.getUID(), options);
}
return super.getStateDescription(channel, original, locale);
}
private @Nullable String getLabel(Stream stream) {
if (stream.getType().equals("internetradio")) {
return stream.getName();
} else {
return stream.getType().substring(0, 1).toUpperCase() + stream.getType().substring(1);
}
}
}

View File

@ -0,0 +1,65 @@
/**
* 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;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.amplipi.internal.model.Preset;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.CommandDescription;
import org.openhab.core.types.CommandOption;
/**
* This class provides the list of valid commands for the preset channel.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@NonNullByDefault
public class PresetCommandOptionProvider extends BaseDynamicCommandDescriptionProvider implements ThingHandlerService {
private @Nullable AmpliPiHandler handler;
@Override
public void setThingHandler(ThingHandler handler) {
this.handler = (AmpliPiHandler) handler;
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
@Override
public @Nullable CommandDescription getCommandDescription(Channel channel,
@Nullable CommandDescription originalCommandDescription, @Nullable Locale locale) {
ChannelTypeUID typeUID = channel.getChannelTypeUID();
List<CommandOption> options = new ArrayList<>();
if (typeUID != null && AmpliPiBindingConstants.CHANNEL_PRESET.equals(typeUID.getId()) && handler != null) {
List<Preset> presets = handler.getPresets();
for (Preset preset : presets) {
options.add(new CommandOption(preset.getId().toString(), preset.getName()));
}
setCommandOptions(channel.getUID(), options);
}
return super.getCommandDescription(channel, originalCommandDescription, locale);
}
}

View File

@ -0,0 +1,67 @@
/**
* 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.discovery;
import java.util.Set;
import javax.jmdns.ServiceInfo;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.amplipi.internal.AmpliPiBindingConstants;
import org.openhab.core.config.discovery.DiscoveryResult;
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;
/**
* This is a discovery participant which finds AmpliPis on the local network
* through their mDNS announcements.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@NonNullByDefault
public class AmpliPiMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Set.of(AmpliPiBindingConstants.THING_TYPE_CONTROLLER);
}
@Override
public String getServiceType() {
return "_http._tcp";
}
@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())
.withRepresentationProperty(AmpliPiBindingConstants.CFG_PARAM_HOSTNAME).build();
return result;
} else {
return null;
}
}
@Override
public @Nullable ThingUID getThingUID(ServiceInfo service) {
// TODO: Currently, the AmpliPi does not seem to announce any services.
return null;
}
}

View File

@ -0,0 +1,115 @@
/**
* 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.discovery;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.amplipi.internal.AmpliPiBindingConstants;
import org.openhab.binding.amplipi.internal.AmpliPiHandler;
import org.openhab.binding.amplipi.internal.AmpliPiStatusChangeListener;
import org.openhab.binding.amplipi.internal.model.Group;
import org.openhab.binding.amplipi.internal.model.Status;
import org.openhab.binding.amplipi.internal.model.Zone;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
/**
* This class discoveres the available zones and groups of the AmpliPi system.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@NonNullByDefault
public class AmpliPiZoneAndGroupDiscoveryService extends AbstractDiscoveryService
implements ThingHandlerService, AmpliPiStatusChangeListener {
private static final int TIMEOUT = 10;
private @Nullable AmpliPiHandler handler;
private List<Zone> zones = List.of();
private List<Group> groups = List.of();
public AmpliPiZoneAndGroupDiscoveryService() throws IllegalArgumentException {
super(Set.of(AmpliPiBindingConstants.THING_TYPE_GROUP, AmpliPiBindingConstants.THING_TYPE_ZONE), TIMEOUT, true);
}
@Override
public void setThingHandler(ThingHandler handler) {
AmpliPiHandler ampliPiHander = (AmpliPiHandler) handler;
ampliPiHander.addStatusChangeListener(this);
this.handler = ampliPiHander;
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
@Override
protected void startScan() {
for (Zone z : zones) {
if (!z.getDisabled()) {
createZone(z);
}
}
for (Group g : groups) {
createGroup(g);
}
}
private void createZone(Zone z) {
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())
.withProperty(AmpliPiBindingConstants.CFG_PARAM_ID, z.getId()).withBridge(bridgeUID)
.withRepresentationProperty(AmpliPiBindingConstants.CFG_PARAM_ID).build();
thingDiscovered(result);
}
}
private void createGroup(Group g) {
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())
.withProperty(AmpliPiBindingConstants.CFG_PARAM_ID, g.getId()).withBridge(bridgeUID)
.withRepresentationProperty(AmpliPiBindingConstants.CFG_PARAM_ID).build();
thingDiscovered(result);
}
}
@Override
public void deactivate() {
if (handler != null) {
handler.removeStatusChangeListener(this);
}
super.deactivate();
}
@Override
public void receive(Status status) {
zones = status.getZones();
groups = status.getGroups();
if (isBackgroundDiscoveryEnabled()) {
startScan();
}
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="amplipi" 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>AmpliPi Binding</name>
<description>This is the binding for the AmpliPi Home Audio System from MicroNova.</description>
</binding:binding>

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="amplipi"
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">
<bridge-type id="controller">
<label>AmpliPi Controller</label>
<description>An AmpliPi controller</description>
<channels>
<channel id="preset" typeId="preset"/>
<channel id="input1" typeId="input">
<label>Source 1</label>
<description>This channel selects the input for source 1.</description>
</channel>
<channel id="input2" typeId="input">
<label>Source 2</label>
<description>This channel selects the input for source 2.</description>
</channel>
<channel id="input3" typeId="input">
<label>Source 3</label>
<description>This channel selects the input for source 3.</description>
</channel>
<channel id="input4" typeId="input">
<label>Source 4</label>
<description>This channel selects the input for source 4.</description>
</channel>
</channels>
<config-description>
<parameter name="hostname" type="text" required="true">
<context>network-address</context>
<label>Hostname</label>
<description>Hostname or IP address of the AmpliPi</description>
</parameter>
<parameter name="refreshInterval" type="integer" unit="s" min="1">
<label>Refresh Interval</label>
<description>Interval the device is polled in sec.</description>
<default>10</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="zone">
<supported-bridge-type-refs>
<bridge-type-ref id="controller"/>
</supported-bridge-type-refs>
<label>AmpliPi Zone</label>
<description>A zone of the AmpliPi system</description>
<channels>
<channel typeId="system.volume" id="volume"/>
<channel typeId="system.mute" id="mute"/>
<channel typeId="source" id="source"/>
</channels>
<config-description>
<parameter name="id" type="integer" required="true">
<label>Zone ID</label>
<description>The ID of the zone</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="group">
<supported-bridge-type-refs>
<bridge-type-ref id="controller"/>
</supported-bridge-type-refs>
<label>AmpliPi Group</label>
<description>A group of the AmpliPi system</description>
<channels>
<channel typeId="system.volume" id="volume"/>
<channel typeId="system.mute" id="mute"/>
<channel typeId="source" id="source"/>
</channels>
<config-description>
<parameter name="id" type="integer" required="true">
<label>Group ID</label>
<description>The ID of the group</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="preset">
<item-type>Number</item-type>
<label>Preset</label>
<description>Choose an existing preset</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="input">
<item-type>String</item-type>
<label>Input</label>
<description>The selected input for the source</description>
<autoUpdatePolicy>recommend</autoUpdatePolicy>
</channel-type>
<channel-type id="source">
<item-type>Number</item-type>
<label>Source</label>
<description>The audio source that is played</description>
<state>
<options>
<option value="0">Source 1</option>
<option value="1">Source 2</option>
<option value="2">Source 3</option>
<option value="3">Source 4</option>
</options>
</state>
<autoUpdatePolicy>recommend</autoUpdatePolicy>
</channel-type>
</thing:thing-descriptions>

View File

@ -52,6 +52,7 @@
<module>org.openhab.binding.amazondashbutton</module>
<module>org.openhab.binding.amazonechocontrol</module>
<module>org.openhab.binding.ambientweather</module>
<module>org.openhab.binding.amplipi</module>
<module>org.openhab.binding.androiddebugbridge</module>
<module>org.openhab.binding.astro</module>
<module>org.openhab.binding.atlona</module>