├── .classpath ├── .project ├── .settings └── org.eclipse.m2e.core.prefs ├── 3rd party ├── YXC_API_Spec_Advanced.pdf └── YXC_API_Spec_Basic-2.00.pdf ├── NOTICE ├── README.md ├── pom.xml ├── src └── main │ ├── feature │ └── feature.xml │ ├── history │ └── dependencies.xml │ ├── java │ └── org │ │ └── openhab │ │ └── binding │ │ └── yamahamusiccast │ │ └── internal │ │ ├── YamahaMusiccastBridgeHandler.java │ │ ├── dto │ │ ├── DeviceInfo.java │ │ ├── DistributionInfo.java │ │ ├── Features.java │ │ ├── PlayInfo.java │ │ ├── PresetInfo.java │ │ ├── RecentInfo.java │ │ ├── Response.java │ │ ├── Status.java │ │ └── UdpMessage.java │ │ ├── yamahamusiccastBindingConstants.java │ │ ├── yamahamusiccastConfiguration.java │ │ ├── yamahamusiccastHandler.java │ │ ├── yamahamusiccastHandlerFactory.java │ │ └── yamahamusiccastStateDescriptionProvider.java │ └── resources │ └── OH-INF │ ├── binding │ └── binding.xml │ └── thing │ ├── bridge.xml │ └── thing-types.xml └── target ├── classes └── org │ └── openhab │ └── binding │ └── yamahamusiccast │ └── internal │ ├── YamahaMusiccastBindingConstants.class │ ├── YamahaMusiccastBridgeHandler.class │ ├── YamahaMusiccastConfiguration.class │ ├── YamahaMusiccastHandler.class │ ├── YamahaMusiccastHandlerFactory.class │ ├── YamahaMusiccastStateDescriptionProvider.class │ └── dto │ ├── DeviceInfo.class │ ├── DistributionInfo$ClientList.class │ ├── DistributionInfo.class │ ├── Features$System.class │ ├── Features.class │ ├── PlayInfo.class │ ├── PresetInfo.class │ ├── RecentInfo.class │ ├── Response.class │ ├── Status.class │ ├── UdpMessage$Dist.class │ ├── UdpMessage$NetUSB.class │ ├── UdpMessage$PresetControl.class │ ├── UdpMessage$Zone.class │ └── UdpMessage.class ├── org.openhab.binding.yamahamusiccast-3.1.0-v0.79.jar ├── org.openhab.binding.yamahamusiccast-3.1.0-v0.80-test.jar ├── org.openhab.binding.yamahamusiccast-3.1.0-v0.81-beta.jar ├── org.openhab.binding.yamahamusiccast-3.1.0-v0.82-beta.jar ├── org.openhab.binding.yamahamusiccast-3.1.0-v0.83-beta.jar ├── org.openhab.binding.yamahamusiccast-3.1.0-v0.84-beta.jar └── org.openhab.binding.yamahamusiccast-3.3.0.jar /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.openhab.binding.yamahamusiccast 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | 25 | 1629230786602 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /3rd party/YXC_API_Spec_Advanced.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/3rd party/YXC_API_Spec_Advanced.pdf -------------------------------------------------------------------------------- /3rd party/YXC_API_Spec_Basic-2.00.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/3rd party/YXC_API_Spec_Basic-2.00.pdf -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This content is produced and maintained by the openHAB project. 2 | 3 | * Project home: https://www.openhab.org 4 | 5 | == Declared Project Licenses 6 | 7 | This program and the accompanying materials are made available under the terms 8 | of the Eclipse Public License 2.0 which is available at 9 | https://www.eclipse.org/legal/epl-2.0/. 10 | 11 | == Source Code 12 | 13 | https://github.com/openhab/openhab-addons 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yamaha MusicCast Binding 2 | 3 | Binding to control Yamaha models via their MusicCast protocol (aka Yamaha Extended Control). 4 | With support for 4 zones : main, zone2, zone3, zone4. Main is always present. Zone2, Zone3, Zone4 are read from the model. 5 | 6 | UDP events are captured to reflect changes in the binding for 7 | 8 | - Power 9 | - Mute 10 | - Volume 11 | - Input 12 | - Presets 13 | - Sleep 14 | - Artist 15 | - Track 16 | - Album 17 | - Album Art 18 | - Repeat 19 | - Shuffle 20 | - Play Time 21 | - Total Time 22 | - Musiccast Link 23 | 24 | ## Supported Things 25 | 26 | Each model (AV Receiver, ...) is a Thing (Thing Type ID: yamahamusiccast:device). Things are linked to a Bridge (Thing Type ID: yamahamusiccast:bridge) for receiving UDP events. 27 | 28 | ## Discovery 29 | 30 | No auto discovery 31 | 32 | ## Thing Configuration 33 | 34 | | Parameter | Type | Description | Advanced | Required | 35 | |--------------------|---------|---------------------------------------------------------|----------|---------------| 36 | | host | String | IP address of the Yamaha model (AVR, ...) | false | true | 37 | | syncVolume | Boolean | Sync volume across linked models (default=false) | false | false | 38 | | defaultAfterMCLink | String | Default Input value for client when MC Link is broken | false | false | 39 | 40 | Default value for *defaultAfterMCLink* is *NET RADIO* as most of the models have this on board. 41 | 42 | ## Channels 43 | 44 | | channel | type | description | 45 | |----------------|--------|---------------------------------------------------------------------| 46 | | power | Switch | Power ON/OFF | 47 | | mute | Switch | Mute ON/OFF | 48 | | volume | Dimmer | Volume as % (recalculated based on Max Volume Model) | 49 | | volumeAbs | Number | Volume as absolute value | 50 | | input | String | See below for list | 51 | | soundProgram | String | See below for list | 52 | | selectPreset | String | Select Netradio/USB preset (fetched from Model) | 53 | | sleep | Number | Fixed values for Sleep : 0/30/60/90/120 in minutes | 54 | | recallScene | Number | Select a scene (8 defaults scenes are foreseen) | 55 | | player | Player | PLAY/PAUSE/NEXT/PREVIOUS/REWIND/FASTFORWARD | 56 | | artist | String | Artist | 57 | | track | String | Track | 58 | | album | String | Album | 59 | | albumArt | Image | Album Art | 60 | | repeat | String | Toggle Repeat. Available values: Off, One, All | 61 | | shuffle | String | Toggle Shuffle. Available values: Off, On, Songs, Album | 62 | | playTime | String | Play time of current selection: radio, song, track, ... | 63 | | totalTime | String | Total time of current selection: radio, song, track, ... | 64 | | mclinkStatus | String | Select your Musiccast Server or set to Standalone, Server or Client | 65 | 66 | 67 | | Zones | description | 68 | |----------------------|------------------------------------------------------| 69 | | zone1-4 | Zone 1 to 4 to control Power, Volume, ... | 70 | | playerControls | Separate zone for Play, Pause, ... | 71 | 72 | ## Input List 73 | 74 | Firmware v1 75 | 76 | cd / tuner / multi_ch / phono / hdmi1 / hdmi2 / hdmi3 / hdmi4 / hdmi5 / hdmi6 / hdmi7 / 77 | hdmi8 / hdmi / av1 / av2 / av3 / av4 / av5 / av6 / av7 / v_aux / aux1 / aux2 / aux / audio1 / 78 | audio2 / audio3 / audio4 / audio_cd / audio / optical1 / optical2 / optical / coaxial1 / coaxial2 / 79 | coaxial / digital1 / digital2 / digital / line1 / line2 / line3 / line_cd / analog / tv / bd_dvd / 80 | usb_dac / usb / bluetooth / server / net_radio / rhapsody / napster / pandora / siriusxm / 81 | spotify / juke / airplay / radiko / qobuz / mc_link / main_sync / none 82 | 83 | Firmware v2 84 | 85 | cd / tuner / multi_ch / phono / hdmi1 / hdmi2 / hdmi3 / hdmi4 / hdmi5 / hdmi6 / hdmi7 / 86 | hdmi8 / hdmi / av1 / av2 / av3 / av4 / av5 / av6 / av7 / v_aux / aux1 / aux2 / aux / audio1 / 87 | audio2 / audio3 / audio4 / **audio5** / audio_cd / audio / optical1 / optical2 / optical / coaxial1 / coaxial2 / 88 | coaxial / digital1 / digital2 / digital / line1 / line2 / line3 / line_cd / analog / tv / bd_dvd / 89 | usb_dac / usb / bluetooth / server / net_radio / ~~rhapsody~~ /napster / pandora / siriusxm / 90 | spotify / juke / airplay / radiko / qobuz / **tidal** / **deezer** / mc_link / main_sync / none 91 | 92 | ## Sound Program 93 | 94 | munich_a / munich_b / munich / frankfurt / stuttgart / vienna / amsterdam / usa_a / usa_b / 95 | tokyo / freiburg / royaumont / chamber / concert / village_gate / village_vanguard / 96 | warehouse_loft / cellar_club / jazz_club / roxy_theatre / bottom_line / arena / sports / 97 | action_game / roleplaying_game / game / music_video / music / recital_opera / pavilion / 98 | disco / standard / spectacle / sci-fi / adventure / drama / talk_show / tv_program / 99 | mono_movie / movie / enhanced / 2ch_stereo / 5ch_stereo / 7ch_stereo / 9ch_stereo / 100 | 11ch_stereo / stereo / surr_decoder / my_surround / target / straight / off 101 | 102 | ## Full Example 103 | 104 | ### Bridge & Thing(s) 105 | 106 | ``` 107 | Bridge yamahamusiccast:bridge:virtual "YXC Bridge" { 108 | Thing yamahamusiccast:device:Living "YXC Living" [host="1.2.3.4"] 109 | } 110 | ``` 111 | 112 | ### Basic setup 113 | 114 | ``` 115 | Switch YamahaPower "" {channel="yamahamusiccast:device:Living:main#power"} 116 | Switch YamahaMute "" {channel="yamahamusiccast:device:Living:main#mute"} 117 | Dimmer YamahaVolume "" {channel="yamahamusiccast:device:Living:main#volume"} 118 | Number YamahaVolumeAbs "" {channel="yamahamusiccast:device:Living:main#volumeAbs"} 119 | String YamahaInput "" {channel="yamahamusiccast:device:Living:main#input"} 120 | String YamahaSelectPreset "" {channel="yamahamusiccast:device:Living:main#selectPreset"} 121 | String YamahaSoundProgram "" {channel="yamahamusiccast:device:Living:main#soundProgram"} 122 | ``` 123 | 124 | ### Player controls 125 | 126 | ``` 127 | Player YamahaPlayer "" {channel="yamahamusiccast:device:Living:playerControls#player"} 128 | String YamahaArt "" {channel="yamahamusiccast:device:Living:playerControls#albumArt"} 129 | String YamahaArtist "" {channel="yamahamusiccast:device:Living:playerControls#artist"} 130 | String YamahaTrack "" {channel="yamahamusiccast:device:Living:playerControls#track"} 131 | String YamahaAlbum "" {channel="yamahamusiccast:device:Living:playerControls#album"} 132 | ``` 133 | 134 | ### MusicCast setup 135 | 136 | The idea here is to select what device/model will be the master. This needs to be done per device/model which will then be the slave. 137 | If you want the *Living* to be the master for the *Kitchen*, select *Living - zone (IP)* from the thing *Kitchen*. 138 | The binding will check if there is already a group active for which *Living* is the master. If yes, this group will be used and *Kitchen* will be added. 139 | If not, a new group will be created. 140 | 141 | *Device A*: Living with IP 192.168.1.1 142 | *Device B*: Kitchen with IP 192.168.1.2 143 | 144 | Set **mclinkStatus** to *Standalone* to remove the device/model from the current active group. The group will keep on exist with other devices/models. 145 | If the device/model is the server, the group will be disbanded. 146 | 147 | ``` 148 | String YamahaMCLinkStatus "" {channel="yamahamusiccast:device:Living:main#mclinkStatus"} 149 | ``` 150 | 151 | During testing with the Yamaha Musiccast app, when removing a slave from the group, the status of the client remained *client* and **input** stayed on *mclink*. Only when changing input, the slave was set to *standalone*. Therefor you can set the parameter **defaultAfterMCLink** to an input value supported by your device to break the whole Musiccast Link in OH. 152 | 153 | #### How to use this in a rule? 154 | 155 | The label uses the format _Thinglabel - zone (IP)_. 156 | The value which is sent to OH uses the format _IP***zone_. 157 | 158 | ``` 159 | sendCommand(Kitchen_YamahaMCServer, "192.168.1.1***main") 160 | sendCommand(Kitchen_YamahaMCServer, "") 161 | sendCommand(Kitchen_YamahaMCServer, "server") 162 | sendCommand(Kitchen_YamahaMCServer, "client") 163 | ``` 164 | 165 | ## Tested Models 166 | 167 | RX-D485 / WX-010 / WX-030 / ISX-80 / YSP-1600 / RX-A860 / R-N303D / EX-A1080 / WXA-050 / HTR-4068 (RX-V479) 168 | MusicCast 20 / WCX-50 / RX-V6A / YAS-306 / ISX-18D / WX-021 / YAS-408 169 | 170 | ## Changelog 171 | 172 | ###### To Do / Wishlist (last updated 17 Feb 2021) 173 | 174 | - [ ] Create a pull request for OH3 (in progress, working on requested changes). 175 | - [ ] Register binding as Audio Sink (currently not possible). 176 | - [ ] Research if it is possible to only change volume of Master without changing config. 177 | - [ ] Autodiscovery (no plans). 178 | - [ ] One central power switch (no plans as not available in API). 179 | 180 | ###### v0.8x - In development 181 | 182 | - Added channels for TotalTime and PlayTime updated with UDP events (v0.80). 183 | - UDP event for mc_link are caught. Server/Client/Standalone are filled (v0.80). 184 | - Another set of changes to avoid null values and be compliant with coding guidelines for Pull Request OH3 (v0.80). 185 | - **BREAKING CHANGE**: _mcServer_ and _unlinkMCServer_ are replaced by _mclinkStatus_. Use this channel to select your Musiccast Server or set to Standalone, Server or Client (v0.81). 186 | - Channels and zones are created dynamically based on number of zones supported by your model (v0.81). 187 | - The new channel _mclinkStatus_ will also show the number of connected clients in case Thing is acting as server (v0.81). 188 | - The channel _recallScene_ now has 8 defaults scenes numbered from 1 to 8 (v0.81). 189 | - Small bugfixes (v0.82/v0.83). 190 | - Sometimes scheduler for UDP events fails. When powering on device via binding, scheduler is checked and started if missing (v0.84). 191 | 192 | ###### v0.7x 193 | 194 | - **BREAKING CHANGE**: Added a bridge to receive UDP events (Power, Mute, Volume, Input) by your OpenHAB instance from various devices. Each Thing will keep the connection alive. UDP events will be dispatched to the corresponding Thing (v0.70). 195 | - channelVolumeAbs has been added to allow to set Volume in absolute value (v0.71). 196 | - Code clean up for Music Cast Link to support common Volume for linked models (v0.72). 197 | - UDP events now support PLAY/PAUSE/FFW/REW/Artist/Track/Album (v0.72). 198 | - Removed refreshjob as UDP events now support Presets/Sleep (v0.73). 199 | - Removed configuration for refreshInterval (v0.74). 200 | - Added channel for AlbumArt/Shuffle/Repeat (v0.74). 201 | - Fixed error which occured when updating Thing (v0.74). 202 | - Other Things detected via Bridge instead of API (v0.74). 203 | - Revert changes for Sync Volume and detect other Things via Bridge (v0.75). 204 | - 2nd try for Sync Volume and detect other Things via Bridge (v0.76). 205 | - Changed *empty value* to *Standalone* (v0.76). 206 | - Update environment to OH 2.5.12, worked further on coding guidelines (v0.77). 207 | - Changes to avoid null values and be compliant with coding guidelines for Pull Request OH3 (v0.78). 208 | - **BREAKING CHANGE**: Thing type renamed from _Device_ to _device_ (v0.79). 209 | - **BREAKING CHANGE**: Configuration parameter renamed from _configHost_ to _host_ (v0.79). 210 | - **BREAKING CHANGE**: Configuration parameter renamed from _configSyncVolume_ to _syncVolume_ (v0.79). 211 | - **BREAKING CHANGE**: Removed the word _channel_ in Channel names and Channel names are changed from upper to lower case.(v0.79). 212 | - Set client to _Standalone_ when input is changed (v0.79). 213 | 214 | ###### v0.60 215 | 216 | - **BREAKING CHANGE**: configuration parameters renamed. "config_host" is replaced with "configHost", "config_refreshInterval" is replaced with "configRefreshInterval" 217 | - Added Artist, Track and Album to the playerControls 218 | - When error occurs, 1 lines is saved instead of whole stacktrace 219 | - Presets are now shown with a number 220 | 221 | ###### v0.50 222 | 223 | - Number of zones are read from device, removed this from configuration 224 | - Support added for Music Cast Link: channelMCServer and channelUnlinkMCServer have been added 225 | - channelRecallScene has been added to select a Scene 226 | 227 | ###### v0.40 228 | 229 | - Added Zone Support 230 | - Favorites are fetched and made available as options 231 | - Various changes under the hood 232 | 233 | ###### v0.30 / v 0.20 / v0.10 234 | 235 | - Initial commits for basic functionality (Power, Mute, Input, ...) 236 | 237 | ###### v0.01 238 | 239 | - Started from skeleton 240 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | 8 | org.openhab.addons.bundles 9 | org.openhab.addons.reactor.bundles 10 | 3.3.0-SNAPSHOT 11 | 12 | 13 | org.openhab.binding.yamahamusiccast 14 | 15 | openHAB Add-ons :: Bundles :: Yamaha Musiccast Binding 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/feature/feature.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features 4 | 5 | 6 | openhab-runtime-base 7 | openhab-transport-upnp 8 | mvn:org.openhab.addons.bundles/org.openhab.binding.yamahamusiccast/${project.version} 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/history/dependencies.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | openhab-runtime-base 5 | openhab-transport-upnp 6 | wrap 7 | mvn:org.openhab.addons.bundles/org.openhab.binding.yamahamusiccast/3.3.0-SNAPSHOT 8 | wrap:mvn:org.lastnpe.eea/eea-all/2.2.1 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastBridgeHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal; 14 | 15 | import java.io.IOException; 16 | import java.net.DatagramPacket; 17 | import java.net.DatagramSocket; 18 | import java.net.InetSocketAddress; 19 | import java.net.SocketException; 20 | import java.net.SocketTimeoutException; 21 | import java.util.UUID; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Executors; 24 | import java.util.concurrent.Future; 25 | 26 | import org.eclipse.jdt.annotation.NonNullByDefault; 27 | import org.eclipse.jdt.annotation.Nullable; 28 | import org.openhab.binding.yamahamusiccast.internal.dto.UdpMessage; 29 | import org.openhab.core.common.NamedThreadFactory; 30 | import org.openhab.core.thing.Bridge; 31 | import org.openhab.core.thing.ChannelUID; 32 | import org.openhab.core.thing.Thing; 33 | import org.openhab.core.thing.ThingStatus; 34 | import org.openhab.core.thing.ThingStatusInfo; 35 | import org.openhab.core.thing.binding.BaseBridgeHandler; 36 | import org.openhab.core.types.Command; 37 | import org.slf4j.Logger; 38 | import org.slf4j.LoggerFactory; 39 | 40 | import com.google.gson.Gson; 41 | 42 | /** 43 | * The {@link YamahaMusiccastBridgeHandler} is responsible for dispatching UDP events to linked Things. 44 | * 45 | * @author Lennert Coopman - Initial contribution 46 | */ 47 | @NonNullByDefault 48 | public class YamahaMusiccastBridgeHandler extends BaseBridgeHandler { 49 | private Gson gson = new Gson(); 50 | private final Logger logger = LoggerFactory.getLogger(YamahaMusiccastBridgeHandler.class); 51 | private String threadname = getThing().getUID().getAsString(); 52 | private @Nullable ExecutorService executor; 53 | private @Nullable Future eventListenerJob; 54 | private static final int UDP_PORT = 41100; 55 | private static final int SOCKET_TIMEOUT_MILLISECONDS = 3000; 56 | private static final int BUFFER_SIZE = 5120; 57 | private @Nullable DatagramSocket socket; 58 | 59 | private void receivePackets() { 60 | try { 61 | DatagramSocket s = new DatagramSocket(null); 62 | s.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS); 63 | s.setReuseAddress(true); 64 | InetSocketAddress address = new InetSocketAddress(UDP_PORT); 65 | s.bind(address); 66 | socket = s; 67 | logger.trace("UDP Listener got socket on port {} with timeout {}", UDP_PORT, SOCKET_TIMEOUT_MILLISECONDS); 68 | } catch (SocketException e) { 69 | logger.trace("UDP Listener got SocketException: {}", e.getMessage(), e); 70 | socket = null; 71 | return; 72 | } 73 | 74 | DatagramPacket packet = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE); 75 | DatagramSocket localSocket = socket; 76 | while (localSocket != null) { 77 | try { 78 | localSocket.receive(packet); 79 | String received = new String(packet.getData(), 0, packet.getLength()); 80 | String trackingID = UUID.randomUUID().toString().replace("-", "").substring(0, 32); 81 | logger.trace("Received packet: {} (Tracking: {})", received, trackingID); 82 | handleUDPEvent(received, trackingID); 83 | } catch (SocketTimeoutException e) { 84 | // Nothing to do on socket timeout 85 | } catch (IOException e) { 86 | logger.trace("UDP Listener got IOException waiting for datagram: {}", e.getMessage()); 87 | localSocket = null; 88 | } 89 | } 90 | logger.trace("UDP Listener exiting"); 91 | } 92 | 93 | public YamahaMusiccastBridgeHandler(Bridge bridge) { 94 | super(bridge); 95 | } 96 | 97 | @Override 98 | public void handleCommand(ChannelUID channelUID, Command command) { 99 | } 100 | 101 | @Override 102 | public void initialize() { 103 | updateStatus(ThingStatus.ONLINE); 104 | executor = Executors.newSingleThreadExecutor(new NamedThreadFactory(threadname)); 105 | Future localEventListenerJob = eventListenerJob; 106 | ExecutorService localExecutor = executor; 107 | if (localEventListenerJob == null || localEventListenerJob.isCancelled()) { 108 | if (localExecutor != null) { 109 | localEventListenerJob = localExecutor.submit(this::receivePackets); 110 | } 111 | } 112 | } 113 | 114 | @Override 115 | public void dispose() { 116 | super.dispose(); 117 | Future localEventListenerJob = eventListenerJob; 118 | ExecutorService localExecutor = executor; 119 | if (localEventListenerJob != null) { 120 | localEventListenerJob.cancel(true); 121 | localEventListenerJob = null; 122 | } 123 | if (localExecutor != null) { 124 | localExecutor.shutdownNow(); 125 | localExecutor = null; 126 | } 127 | } 128 | 129 | public void handleUDPEvent(String json, String trackingID) { 130 | String udpDeviceId = ""; 131 | Bridge bridge = (Bridge) thing; 132 | for (Thing thing : bridge.getThings()) { 133 | ThingStatusInfo statusInfo = thing.getStatusInfo(); 134 | switch (statusInfo.getStatus()) { 135 | case ONLINE: 136 | logger.trace("Thing Status: ONLINE - {}", thing.getLabel()); 137 | YamahaMusiccastHandler handler = (YamahaMusiccastHandler) thing.getHandler(); 138 | if (handler != null) { 139 | logger.trace("UDP: {} - {} ({} - Tracking: {})", json, handler.getDeviceId(), thing.getLabel(), 140 | trackingID); 141 | 142 | @Nullable 143 | UdpMessage targetObject = gson.fromJson(json, UdpMessage.class); 144 | if (targetObject != null) { 145 | udpDeviceId = targetObject.getDeviceId(); 146 | if (udpDeviceId.equals(handler.getDeviceId())) { 147 | handler.processUDPEvent(json, trackingID); 148 | } 149 | } 150 | } 151 | break; 152 | default: 153 | logger.trace("Thing Status: NOT ONLINE - {} (Tracking: {})", thing.getLabel(), trackingID); 154 | break; 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/dto/DeviceInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal.dto; 14 | 15 | import com.google.gson.annotations.SerializedName; 16 | 17 | /** 18 | * This class represents the DeviceInfo request requested from the Yamaha model/device via the API. 19 | * 20 | * @author Lennert Coopman - Initial contribution 21 | */ 22 | public class DeviceInfo { 23 | 24 | @SerializedName("response_code") 25 | private String responseCode; 26 | 27 | @SerializedName("model_name") 28 | private String modelName; 29 | 30 | @SerializedName("device_id") 31 | private String deviceId; 32 | 33 | public String getResponseCode() { 34 | if (responseCode == null) { 35 | responseCode = ""; 36 | } 37 | return responseCode; 38 | } 39 | 40 | public String getModelName() { 41 | if (modelName == null) { 42 | modelName = ""; 43 | } 44 | return modelName; 45 | } 46 | 47 | public String getDeviceId() { 48 | if (deviceId == null) { 49 | deviceId = ""; 50 | } 51 | return deviceId; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/dto/DistributionInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal.dto; 14 | 15 | import com.google.gson.JsonArray; 16 | import com.google.gson.annotations.SerializedName; 17 | 18 | /** 19 | * This class represents the DistributionInfo request requested from the Yamaha model/device via the API. 20 | * 21 | * @author Lennert Coopman - Initial contribution 22 | */ 23 | public class DistributionInfo { 24 | 25 | @SerializedName("response_code") 26 | private String responseCode; 27 | 28 | @SerializedName("group_id") 29 | private String groupId; 30 | 31 | @SerializedName("role") 32 | private String role; 33 | 34 | @SerializedName("server_zone") 35 | private String serverZone; 36 | 37 | @SerializedName("client_list") 38 | private JsonArray clientList; 39 | 40 | public String getResponseCode() { 41 | if (responseCode == null) { 42 | responseCode = ""; 43 | } 44 | return responseCode; 45 | } 46 | 47 | public String getGroupId() { 48 | if (groupId == null) { 49 | groupId = ""; 50 | } 51 | return groupId; 52 | } 53 | 54 | public String getRole() { 55 | if (role == null) { 56 | role = ""; 57 | } 58 | return role; 59 | } 60 | 61 | public String getServerZone() { 62 | if (serverZone == null) { 63 | serverZone = ""; 64 | } 65 | return serverZone; 66 | } 67 | 68 | public JsonArray getClientList() { 69 | return clientList; 70 | } 71 | 72 | public class ClientList { 73 | @SerializedName("ip_address") 74 | private String ipaddress; 75 | 76 | public String getIpaddress() { 77 | if (ipaddress == null) { 78 | ipaddress = ""; 79 | } 80 | return ipaddress; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/dto/Features.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal.dto; 14 | 15 | import com.google.gson.annotations.SerializedName; 16 | 17 | /** 18 | * This class represents the Features request requested from the Yamaha model/device via the API. 19 | * 20 | * @author Lennert Coopman - Initial contribution 21 | */ 22 | 23 | public class Features { 24 | 25 | @SerializedName("response_code") 26 | private String responseCode; 27 | 28 | public String getResponseCode() { 29 | if (responseCode == null) { 30 | responseCode = ""; 31 | } 32 | return responseCode; 33 | } 34 | 35 | @SerializedName("system") 36 | private System system; 37 | 38 | public System getSystem() { 39 | return system; 40 | } 41 | 42 | public class System { 43 | @SerializedName("zone_num") 44 | private int zoneNum = 0; 45 | 46 | public int getZoneNum() { 47 | return zoneNum; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/dto/PlayInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal.dto; 14 | 15 | import com.google.gson.annotations.SerializedName; 16 | 17 | /** 18 | * This class represents the PlayInfo request requested from the Yamaha model/device via the API. 19 | * 20 | * @author Lennert Coopman - Initial contribution 21 | */ 22 | 23 | public class PlayInfo { 24 | 25 | @SerializedName("response_code") 26 | private String responseCode; 27 | 28 | @SerializedName("playback") 29 | private String playback; 30 | 31 | @SerializedName("artist") 32 | private String artist; 33 | 34 | @SerializedName("track") 35 | private String track; 36 | 37 | @SerializedName("album") 38 | private String album; 39 | 40 | @SerializedName("albumart_url") 41 | private String albumarturl; 42 | 43 | @SerializedName("repeat") 44 | private String repeat; 45 | 46 | @SerializedName("shuffle") 47 | private String shuffle; 48 | 49 | @SerializedName("play_time") 50 | private int playTime = 0; 51 | 52 | @SerializedName("total_time") 53 | private int totalTime = 0; 54 | 55 | public String getResponseCode() { 56 | if (responseCode == null) { 57 | responseCode = ""; 58 | } 59 | return responseCode; 60 | } 61 | 62 | public String getPlayback() { 63 | if (playback == null) { 64 | playback = ""; 65 | } 66 | return playback; 67 | } 68 | 69 | public String getArtist() { 70 | if (artist == null) { 71 | artist = ""; 72 | } 73 | return artist; 74 | } 75 | 76 | public String getTrack() { 77 | if (track == null) { 78 | track = ""; 79 | } 80 | return track; 81 | } 82 | 83 | public String getAlbum() { 84 | if (album == null) { 85 | album = ""; 86 | } 87 | return album; 88 | } 89 | 90 | public String getAlbumArtUrl() { 91 | if (albumarturl == null) { 92 | albumarturl = ""; 93 | } 94 | return albumarturl; 95 | } 96 | 97 | public String getRepeat() { 98 | if (repeat == null) { 99 | repeat = ""; 100 | } 101 | return repeat; 102 | } 103 | 104 | public String getShuffle() { 105 | if (shuffle == null) { 106 | shuffle = ""; 107 | } 108 | return shuffle; 109 | } 110 | 111 | public int getPlayTime() { 112 | return playTime; 113 | } 114 | 115 | public int getTotalTime() { 116 | return totalTime; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/dto/PresetInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal.dto; 14 | 15 | import com.google.gson.JsonArray; 16 | import com.google.gson.annotations.SerializedName; 17 | 18 | /** 19 | * This class represents the PresetInfo request requested from the Yamaha model/device via the API. 20 | * 21 | * @author Lennert Coopman - Initial contribution 22 | */ 23 | public class PresetInfo { 24 | 25 | @SerializedName("response_code") 26 | private String responseCode; 27 | 28 | @SerializedName("preset_info") 29 | private JsonArray presetInfo; 30 | 31 | public String getResponseCode() { 32 | if (responseCode == null) { 33 | responseCode = ""; 34 | } 35 | return responseCode; 36 | } 37 | 38 | public JsonArray getPresetInfo() { 39 | return presetInfo; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/dto/RecentInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal.dto; 14 | 15 | import com.google.gson.JsonArray; 16 | import com.google.gson.annotations.SerializedName; 17 | 18 | /** 19 | * This class represents the RecentInfo request requested from the Yamaha model/device via the API. 20 | * 21 | * @author Lennert Coopman - Initial contribution 22 | */ 23 | public class RecentInfo { 24 | 25 | @SerializedName("response_code") 26 | private String responseCode; 27 | 28 | @SerializedName("recent_info") 29 | private JsonArray recentInfo; 30 | 31 | public String getResponseCode() { 32 | if (responseCode == null) { 33 | responseCode = ""; 34 | } 35 | return responseCode; 36 | } 37 | 38 | public JsonArray getRecentInfo() { 39 | return recentInfo; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/dto/Response.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal.dto; 14 | 15 | import com.google.gson.annotations.SerializedName; 16 | 17 | /** 18 | * This class represents the response received from the Yamaha model/device via the API. 19 | * 20 | * @author Lennert Coopman - Initial contribution 21 | */ 22 | 23 | public class Response { 24 | 25 | @SerializedName("response_code") 26 | private String responseCode; 27 | 28 | public String getResponseCode() { 29 | if (responseCode == null) { 30 | responseCode = ""; 31 | } 32 | return responseCode; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/dto/Status.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal.dto; 14 | 15 | import com.google.gson.annotations.SerializedName; 16 | 17 | /** 18 | * This class represents the Status request requested from the Yamaha model/device via the API. 19 | * 20 | * @author Lennert Coopman - Initial contribution 21 | */ 22 | 23 | public class Status { 24 | 25 | @SerializedName("response_code") 26 | private String responseCode; 27 | 28 | @SerializedName("power") 29 | private String power; 30 | 31 | @SerializedName("mute") 32 | private String mute; 33 | 34 | @SerializedName("volume") 35 | private int volume; 36 | 37 | @SerializedName("max_volume") 38 | private int maxVolume = 1; 39 | 40 | @SerializedName("input") 41 | private String input; 42 | 43 | @SerializedName("sound_program") 44 | private String soundProgram; 45 | 46 | @SerializedName("sleep") 47 | private int sleep = 0; 48 | 49 | public String getResponseCode() { 50 | if (responseCode == null) { 51 | responseCode = ""; 52 | } 53 | return responseCode; 54 | } 55 | 56 | public String getPower() { 57 | if (power == null) { 58 | power = ""; 59 | } 60 | return power; 61 | } 62 | 63 | public String getMute() { 64 | if (mute == null) { 65 | mute = ""; 66 | } 67 | return mute; 68 | } 69 | 70 | public int getVolume() { 71 | return volume; 72 | } 73 | 74 | public int getMaxVolume() { 75 | // if no value is returned, set to 1 to avoid division by zero 76 | if (maxVolume == 0) { 77 | maxVolume = 1; 78 | } 79 | return maxVolume; 80 | } 81 | 82 | public String getInput() { 83 | if (input == null) { 84 | input = ""; 85 | } 86 | return input; 87 | } 88 | 89 | public String getSoundProgram() { 90 | if (soundProgram == null) { 91 | soundProgram = ""; 92 | } 93 | return soundProgram; 94 | } 95 | 96 | public int getSleep() { 97 | return sleep; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal.dto; 14 | 15 | import com.google.gson.annotations.SerializedName; 16 | 17 | /** 18 | * This class represents the UDP event received from the Yamaha model/device. 19 | * 20 | * @author Lennert Coopman - Initial contribution 21 | */ 22 | 23 | public class UdpMessage { 24 | 25 | @SerializedName("device_id") 26 | private String deviceId; 27 | 28 | public String getDeviceId() { 29 | if (deviceId == null) { 30 | deviceId = ""; 31 | } 32 | return deviceId; 33 | } 34 | 35 | @SerializedName("main") 36 | private Zone main; 37 | @SerializedName("zone2") 38 | private Zone zone2; 39 | @SerializedName("zone3") 40 | private Zone zone3; 41 | @SerializedName("zone4") 42 | private Zone zone4; 43 | @SerializedName("netusb") 44 | private NetUSB netusb; 45 | @SerializedName("dist") 46 | private Dist dist; 47 | 48 | public Zone getMain() { 49 | return main; 50 | } 51 | 52 | public Zone getZone2() { 53 | return zone2; 54 | } 55 | 56 | public Zone getZone3() { 57 | return zone3; 58 | } 59 | 60 | public Zone getZone4() { 61 | return zone4; 62 | } 63 | 64 | public NetUSB getNetUSB() { 65 | return netusb; 66 | } 67 | 68 | public Dist getDist() { 69 | return dist; 70 | } 71 | 72 | public class Zone { 73 | @SerializedName("power") 74 | private String power; 75 | @SerializedName("volume") 76 | private int volume = 0; 77 | @SerializedName("mute") 78 | private String mute; 79 | @SerializedName("input") 80 | private String input; 81 | @SerializedName("status_updated") 82 | private String statusUpdated; 83 | 84 | public String getPower() { 85 | if (power == null) { 86 | power = ""; 87 | } 88 | return power; 89 | } 90 | 91 | public String getMute() { 92 | if (mute == null) { 93 | mute = ""; 94 | } 95 | return mute; 96 | } 97 | 98 | public String getInput() { 99 | if (input == null) { 100 | input = ""; 101 | } 102 | return input; 103 | } 104 | 105 | public int getVolume() { 106 | return volume; 107 | } 108 | 109 | public String getstatusUpdated() { 110 | if (statusUpdated == null) { 111 | statusUpdated = ""; 112 | } 113 | return statusUpdated; 114 | } 115 | } 116 | 117 | public class NetUSB { 118 | @SerializedName("preset_control") 119 | private PresetControl presetControl; 120 | @SerializedName("play_info_updated") 121 | private String playInfoUpdated; 122 | @SerializedName("play_time") 123 | private int playTime; 124 | 125 | public PresetControl getPresetControl() { 126 | return presetControl; 127 | } 128 | 129 | public String getPlayInfoUpdated() { 130 | if (playInfoUpdated == null) { 131 | playInfoUpdated = ""; 132 | } 133 | return playInfoUpdated; 134 | } 135 | 136 | public int getPlayTime() { 137 | return playTime; 138 | } 139 | } 140 | 141 | public class PresetControl { 142 | @SerializedName("type") 143 | private String type; 144 | @SerializedName("num") 145 | private int num = 1; 146 | @SerializedName("result") 147 | private String result; 148 | 149 | public String getType() { 150 | if (type == null) { 151 | type = ""; 152 | } 153 | return type; 154 | } 155 | 156 | public String getResult() { 157 | if (result == null) { 158 | result = ""; 159 | } 160 | return result; 161 | } 162 | 163 | public int getNum() { 164 | return num; 165 | } 166 | } 167 | 168 | public class Dist { 169 | @SerializedName("dist_info_updated") 170 | private String distInfoUpdated; 171 | 172 | public String getDistInfoUpdated() { 173 | if (distInfoUpdated == null) { 174 | distInfoUpdated = ""; 175 | } 176 | return distInfoUpdated; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/yamahamusiccastBindingConstants.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | import org.openhab.core.thing.ThingTypeUID; 17 | import org.openhab.core.thing.type.ChannelTypeUID; 18 | 19 | /** 20 | * The {@link YamahaMusiccastBindingConstants} class defines common constants, which are 21 | * used across the whole binding. 22 | * 23 | * @author Lennert Coopman - Initial contribution 24 | */ 25 | @NonNullByDefault 26 | public class YamahaMusiccastBindingConstants { 27 | 28 | private static final String BINDING_ID = "yamahamusiccast"; 29 | 30 | // List of all Thing Type UIDs 31 | public static final ThingTypeUID THING_DEVICE = new ThingTypeUID(BINDING_ID, "device"); 32 | public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); 33 | 34 | // List of all Channel Type UIDs 35 | public static final ChannelTypeUID CHANNEL_TYPE_UID_POWER = new ChannelTypeUID("system:power"); 36 | public static final ChannelTypeUID CHANNEL_TYPE_UID_MUTE = new ChannelTypeUID("system:mute"); 37 | public static final ChannelTypeUID CHANNEL_TYPE_UID_VOLUME = new ChannelTypeUID("system:volume"); 38 | public static final ChannelTypeUID CHANNEL_TYPE_UID_VOLUMEABS = new ChannelTypeUID(BINDING_ID, "volumeAbs"); 39 | public static final ChannelTypeUID CHANNEL_TYPE_UID_INPUT = new ChannelTypeUID(BINDING_ID, "input"); 40 | public static final ChannelTypeUID CHANNEL_TYPE_UID_SOUNDPROGRAM = new ChannelTypeUID(BINDING_ID, "soundProgram"); 41 | public static final ChannelTypeUID CHANNEL_TYPE_UID_SELECTPRESET = new ChannelTypeUID(BINDING_ID, "selectPreset"); 42 | public static final ChannelTypeUID CHANNEL_TYPE_UID_SLEEP = new ChannelTypeUID(BINDING_ID, "sleep"); 43 | public static final ChannelTypeUID CHANNEL_TYPE_UID_RECALLSCENE = new ChannelTypeUID(BINDING_ID, "recallScene"); 44 | public static final ChannelTypeUID CHANNEL_TYPE_UID_MCLINKSTATUS = new ChannelTypeUID(BINDING_ID, "mclinkStatus"); 45 | 46 | // List of all Channel ids 47 | public static final String CHANNEL_POWER = "power"; 48 | public static final String CHANNEL_MUTE = "mute"; 49 | public static final String CHANNEL_VOLUME = "volume"; 50 | public static final String CHANNEL_VOLUMEABS = "volumeAbs"; 51 | public static final String CHANNEL_INPUT = "input"; 52 | public static final String CHANNEL_SOUNDPROGRAM = "soundProgram"; 53 | public static final String CHANNEL_SELECTPRESET = "selectPreset"; 54 | public static final String CHANNEL_PLAYER = "player"; 55 | public static final String CHANNEL_SLEEP = "sleep"; 56 | public static final String CHANNEL_RECALLSCENE = "recallScene"; 57 | public static final String CHANNEL_ARTIST = "artist"; 58 | public static final String CHANNEL_TRACK = "track"; 59 | public static final String CHANNEL_ALBUM = "album"; 60 | public static final String CHANNEL_ALBUMART = "albumArt"; 61 | public static final String CHANNEL_REPEAT = "repeat"; 62 | public static final String CHANNEL_SHUFFLE = "shuffle"; 63 | public static final String CHANNEL_MCLINKSTATUS = "mclinkStatus"; 64 | public static final String CHANNEL_PLAYTIME = "playTime"; 65 | public static final String CHANNEL_TOTALTIME = "totalTime"; 66 | 67 | public static final int CONNECTION_TIMEOUT_MILLISEC = 5000; 68 | public static final int LONG_CONNECTION_TIMEOUT_MILLISEC = 60000; 69 | public static final String HTTP = "http://"; 70 | public static final String YAMAHA_EXTENDED_CONTROL = "/YamahaExtendedControl/v1/"; 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/yamahamusiccastConfiguration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | import org.eclipse.jdt.annotation.Nullable; 17 | 18 | /** 19 | * The {@link YamahaMusiccastConfiguration} class contains fields mapping thing configuration parameters. 20 | * 21 | * @author Lennert Coopman - Initial contribution 22 | */ 23 | @NonNullByDefault 24 | public class YamahaMusiccastConfiguration { 25 | 26 | public @Nullable String host; 27 | public @Nullable Boolean syncVolume; 28 | public @Nullable String defaultAfterMCLink; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/yamahamusiccastHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal; 14 | 15 | import static org.openhab.binding.yamahamusiccast.internal.YamahaMusiccastBindingConstants.*; 16 | 17 | import java.io.ByteArrayInputStream; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.nio.charset.StandardCharsets; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Objects; 24 | import java.util.Properties; 25 | import java.util.Random; 26 | import java.util.UUID; 27 | import java.util.concurrent.ScheduledFuture; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | import org.eclipse.jdt.annotation.NonNullByDefault; 31 | import org.eclipse.jdt.annotation.Nullable; 32 | import org.openhab.binding.yamahamusiccast.internal.dto.DeviceInfo; 33 | import org.openhab.binding.yamahamusiccast.internal.dto.DistributionInfo; 34 | import org.openhab.binding.yamahamusiccast.internal.dto.Features; 35 | import org.openhab.binding.yamahamusiccast.internal.dto.PlayInfo; 36 | import org.openhab.binding.yamahamusiccast.internal.dto.PresetInfo; 37 | import org.openhab.binding.yamahamusiccast.internal.dto.RecentInfo; 38 | import org.openhab.binding.yamahamusiccast.internal.dto.Response; 39 | import org.openhab.binding.yamahamusiccast.internal.dto.Status; 40 | import org.openhab.binding.yamahamusiccast.internal.dto.UdpMessage; 41 | import org.openhab.core.io.net.http.HttpUtil; 42 | import org.openhab.core.library.types.DecimalType; 43 | import org.openhab.core.library.types.NextPreviousType; 44 | import org.openhab.core.library.types.OnOffType; 45 | import org.openhab.core.library.types.PercentType; 46 | import org.openhab.core.library.types.PlayPauseType; 47 | import org.openhab.core.library.types.RewindFastforwardType; 48 | import org.openhab.core.library.types.StringType; 49 | import org.openhab.core.thing.Bridge; 50 | import org.openhab.core.thing.Channel; 51 | import org.openhab.core.thing.ChannelUID; 52 | import org.openhab.core.thing.Thing; 53 | import org.openhab.core.thing.ThingStatus; 54 | import org.openhab.core.thing.ThingStatusDetail; 55 | import org.openhab.core.thing.binding.BaseThingHandler; 56 | import org.openhab.core.thing.binding.builder.ChannelBuilder; 57 | import org.openhab.core.thing.binding.builder.ThingBuilder; 58 | import org.openhab.core.thing.type.ChannelTypeUID; 59 | import org.openhab.core.types.Command; 60 | import org.openhab.core.types.RefreshType; 61 | import org.openhab.core.types.StateOption; 62 | import org.slf4j.Logger; 63 | import org.slf4j.LoggerFactory; 64 | 65 | import com.google.gson.Gson; 66 | import com.google.gson.JsonElement; 67 | import com.google.gson.JsonObject; 68 | 69 | /** 70 | * The {@link YamahaMusiccastHandler} is responsible for handling commands, which are 71 | * sent to one of the channels. 72 | * 73 | * @author Lennert Coopman - Initial contribution 74 | */ 75 | @NonNullByDefault 76 | public class YamahaMusiccastHandler extends BaseThingHandler { 77 | private Gson gson = new Gson(); 78 | private Logger logger = LoggerFactory.getLogger(YamahaMusiccastHandler.class); 79 | private @Nullable ScheduledFuture generalHousekeepingTask; 80 | private @Nullable String httpResponse; 81 | private @Nullable String tmpString = ""; 82 | private int volumePercent = 0; 83 | private int volumeAbsValue = 0; 84 | private @Nullable String responseCode = ""; 85 | private int volumeState = 0; 86 | private int maxVolumeState = 0; 87 | private @Nullable String inputState = ""; 88 | private @Nullable String soundProgramState = ""; 89 | private int sleepState = 0; 90 | private @Nullable String artistState = ""; 91 | private @Nullable String trackState = ""; 92 | private @Nullable String albumState = ""; 93 | private @Nullable String repeatState = ""; 94 | private @Nullable String shuffleState = ""; 95 | private int playTimeState = 0; 96 | private int totalTimeState = 0; 97 | private @Nullable String zone = "main"; 98 | private String channelWithoutGroup = ""; 99 | private @Nullable String thingLabel = ""; 100 | private @Nullable String mclinkSetupServer = ""; 101 | private @Nullable String mclinkSetupZone = ""; 102 | private String url = ""; 103 | private String json = ""; 104 | private String action = ""; 105 | private int zoneNum = 0; 106 | private @Nullable String groupId = ""; 107 | private @Nullable String host; 108 | public @Nullable String deviceId = ""; 109 | 110 | private YamahaMusiccastStateDescriptionProvider stateDescriptionProvider; 111 | 112 | public YamahaMusiccastHandler(Thing thing, YamahaMusiccastStateDescriptionProvider stateDescriptionProvider) { 113 | super(thing); 114 | this.stateDescriptionProvider = stateDescriptionProvider; 115 | } 116 | 117 | @Override 118 | public void handleCommand(ChannelUID channelUID, Command command) { 119 | String localValueToCheck = ""; 120 | String localRole = ""; 121 | boolean localSyncVolume; 122 | String localDefaultAfterMCLink = ""; 123 | String localRoleSelectedThing = ""; 124 | if (command != RefreshType.REFRESH) { 125 | logger.trace("Handling command {} for channel {}", command, channelUID); 126 | channelWithoutGroup = channelUID.getIdWithoutGroup(); 127 | zone = channelUID.getGroupId(); 128 | DistributionInfo distributioninfo = new DistributionInfo(); 129 | Response response = new Response(); 130 | switch (channelWithoutGroup) { 131 | case CHANNEL_POWER: 132 | if (command == OnOffType.ON) { 133 | httpResponse = setPower("on", zone, this.host); 134 | response = gson.fromJson(httpResponse, Response.class); 135 | if (response != null) { 136 | localValueToCheck = response.getResponseCode(); 137 | if (!"0".equals(localValueToCheck)) { 138 | updateState(channelUID, OnOffType.OFF); 139 | } 140 | } 141 | // check on scheduler task for UDP events 142 | ScheduledFuture localGeneralHousekeepingTask = generalHousekeepingTask; 143 | if (localGeneralHousekeepingTask == null) { 144 | logger.trace("YXC - No scheduler task found!"); 145 | generalHousekeepingTask = scheduler.scheduleWithFixedDelay(this::generalHousekeeping, 5, 146 | 300, TimeUnit.SECONDS); 147 | 148 | } else { 149 | logger.trace("Scheduler task found!"); 150 | } 151 | 152 | } else if (command == OnOffType.OFF) { 153 | httpResponse = setPower("standby", zone, this.host); 154 | response = gson.fromJson(httpResponse, Response.class); 155 | powerOffCleanup(); 156 | if (response != null) { 157 | localValueToCheck = response.getResponseCode(); 158 | if (!"0".equals(localValueToCheck)) { 159 | updateState(channelUID, OnOffType.ON); 160 | } 161 | } 162 | } 163 | break; 164 | case CHANNEL_MUTE: 165 | if (command == OnOffType.ON) { 166 | httpResponse = setMute("true", zone, this.host); 167 | response = gson.fromJson(httpResponse, Response.class); 168 | if (response != null) { 169 | localValueToCheck = response.getResponseCode(); 170 | if (!"0".equals(localValueToCheck)) { 171 | updateState(channelUID, OnOffType.OFF); 172 | } 173 | } 174 | } else if (command == OnOffType.OFF) { 175 | httpResponse = setMute("false", zone, this.host); 176 | response = gson.fromJson(httpResponse, Response.class); 177 | if (response != null) { 178 | localValueToCheck = response.getResponseCode(); 179 | if (!"0".equals(localValueToCheck)) { 180 | updateState(channelUID, OnOffType.ON); 181 | } 182 | } 183 | } 184 | break; 185 | case CHANNEL_VOLUME: 186 | volumePercent = Integer.parseInt(command.toString().replace(".0", "")); 187 | volumeAbsValue = (maxVolumeState * volumePercent) / 100; 188 | setVolume(volumeAbsValue, zone, this.host); 189 | localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString()); 190 | if (localSyncVolume == Boolean.TRUE) { 191 | tmpString = getDistributionInfo(this.host); 192 | distributioninfo = gson.fromJson(tmpString, DistributionInfo.class); 193 | if (distributioninfo != null) { 194 | localRole = distributioninfo.getRole(); 195 | if ("server".equals(localRole)) { 196 | for (JsonElement ip : distributioninfo.getClientList()) { 197 | JsonObject clientObject = ip.getAsJsonObject(); 198 | setVolumeLinkedDevice(volumePercent, zone, 199 | clientObject.get("ip_address").getAsString()); 200 | } 201 | } 202 | } 203 | } // END config.syncVolume 204 | break; 205 | case CHANNEL_VOLUMEABS: 206 | volumeAbsValue = Integer.parseInt(command.toString().replace(".0", "")); 207 | volumePercent = (volumeAbsValue / maxVolumeState) * 100; 208 | setVolume(volumeAbsValue, zone, this.host); 209 | localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString()); 210 | if (localSyncVolume == Boolean.TRUE) { 211 | tmpString = getDistributionInfo(this.host); 212 | distributioninfo = gson.fromJson(tmpString, DistributionInfo.class); 213 | if (distributioninfo != null) { 214 | localRole = distributioninfo.getRole(); 215 | if ("server".equals(localRole)) { 216 | for (JsonElement ip : distributioninfo.getClientList()) { 217 | JsonObject clientObject = ip.getAsJsonObject(); 218 | setVolumeLinkedDevice(volumePercent, zone, 219 | clientObject.get("ip_address").getAsString()); 220 | } 221 | } 222 | } 223 | } 224 | break; 225 | case CHANNEL_INPUT: 226 | // if it is a client, disconnect it first. 227 | tmpString = getDistributionInfo(this.host); 228 | distributioninfo = gson.fromJson(tmpString, DistributionInfo.class); 229 | if (distributioninfo != null) { 230 | localRole = distributioninfo.getRole(); 231 | if ("client".equals(localRole)) { 232 | json = "{\"group_id\":\"\"}"; 233 | httpResponse = setClientServerInfo(this.host, json, "setClientInfo"); 234 | } 235 | } 236 | setInput(command.toString(), zone, this.host); 237 | break; 238 | case CHANNEL_SOUNDPROGRAM: 239 | setSoundProgram(command.toString(), zone, this.host); 240 | break; 241 | case CHANNEL_SELECTPRESET: 242 | setPreset(command.toString(), zone, this.host); 243 | break; 244 | case CHANNEL_PLAYER: 245 | if (command.equals(PlayPauseType.PLAY)) { 246 | setPlayback("play", this.host); 247 | } else if (command.equals(PlayPauseType.PAUSE)) { 248 | setPlayback("pause", this.host); 249 | } else if (command.equals(NextPreviousType.NEXT)) { 250 | setPlayback("next", this.host); 251 | } else if (command.equals(NextPreviousType.PREVIOUS)) { 252 | setPlayback("previous", this.host); 253 | } else if (command.equals(RewindFastforwardType.REWIND)) { 254 | setPlayback("fast_reverse_start", this.host); 255 | } else if (command.equals(RewindFastforwardType.FASTFORWARD)) { 256 | setPlayback("fast_forward_end", this.host); 257 | } 258 | break; 259 | case CHANNEL_SLEEP: 260 | setSleep(command.toString(), zone, this.host); 261 | break; 262 | case CHANNEL_MCLINKSTATUS: 263 | action = ""; 264 | json = ""; 265 | tmpString = getDistributionInfo(this.host); 266 | distributioninfo = gson.fromJson(tmpString, DistributionInfo.class); 267 | if (distributioninfo != null) { 268 | responseCode = distributioninfo.getResponseCode(); 269 | localRole = distributioninfo.getRole(); 270 | if (command.toString().equals("")) { 271 | action = "unlink"; 272 | groupId = distributioninfo.getGroupId(); 273 | } else if (command.toString().contains("***")) { 274 | action = "link"; 275 | String[] parts = command.toString().split("\\*\\*\\*"); 276 | if (parts.length > 1) { 277 | mclinkSetupServer = parts[0]; 278 | mclinkSetupZone = parts[1]; 279 | tmpString = getDistributionInfo(mclinkSetupServer); 280 | distributioninfo = gson.fromJson(tmpString, DistributionInfo.class); 281 | if (distributioninfo != null) { 282 | responseCode = distributioninfo.getResponseCode(); 283 | localRoleSelectedThing = distributioninfo.getRole(); 284 | groupId = distributioninfo.getGroupId(); 285 | if (localRoleSelectedThing != null) { 286 | if ("server".equals(localRoleSelectedThing)) { 287 | groupId = distributioninfo.getGroupId(); 288 | } else if ("client".equals(localRoleSelectedThing)) { 289 | groupId = ""; 290 | } else if ("none".equals(localRoleSelectedThing)) { 291 | groupId = generateGroupId(); 292 | } 293 | } 294 | } 295 | } 296 | } 297 | 298 | if ("unlink".equals(action)) { 299 | json = "{\"group_id\":\"\"}"; 300 | if (localRole != null) { 301 | if ("server".equals(localRole)) { 302 | httpResponse = setClientServerInfo(this.host, json, "setServerInfo"); 303 | // Set GroupId = "" for linked clients 304 | if (distributioninfo != null) { 305 | for (JsonElement ip : distributioninfo.getClientList()) { 306 | JsonObject clientObject = ip.getAsJsonObject(); 307 | setClientServerInfo(clientObject.get("ip_address").getAsString(), json, 308 | "setClientInfo"); 309 | } 310 | } 311 | } else if ("client".equals(localRole)) { 312 | mclinkSetupServer = connectedServer(); 313 | // Step 1. empty group on client 314 | httpResponse = setClientServerInfo(this.host, json, "setClientInfo"); 315 | // empty zone to respect defaults 316 | if (!"".equals(mclinkSetupServer)) { 317 | // Step 2. remove client from server 318 | json = "{\"group_id\":\"" + groupId 319 | + "\", \"type\":\"remove\", \"client_list\":[\"" + this.host + "\"]}"; 320 | httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo"); 321 | // Step 3. reflect changes to master 322 | httpResponse = startDistribution(mclinkSetupServer); 323 | localDefaultAfterMCLink = getThing().getConfiguration() 324 | .get("defaultAfterMCLink").toString(); 325 | httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host); 326 | } else if ("".equals(mclinkSetupServer)) { 327 | // fallback in case client is removed from group by ending group on server side 328 | localDefaultAfterMCLink = getThing().getConfiguration() 329 | .get("defaultAfterMCLink").toString(); 330 | httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host); 331 | } 332 | } 333 | } 334 | } else if ("link".equals(action)) { 335 | if (localRole != null) { 336 | if ("none".equals(localRole)) { 337 | json = "{\"group_id\":\"" + groupId + "\", \"zone\":\"" + mclinkSetupZone 338 | + "\", \"type\":\"add\", \"client_list\":[\"" + this.host + "\"]}"; 339 | logger.trace("setServerInfo json: {}", json); 340 | httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo"); 341 | // All zones of Model are required for MC Link 342 | tmpString = ""; 343 | for (int i = 1; i <= zoneNum; i++) { 344 | switch (i) { 345 | case 1: 346 | tmpString = "\"main\""; 347 | break; 348 | case 2: 349 | tmpString = tmpString + ", \"zone2\""; 350 | break; 351 | case 3: 352 | tmpString = tmpString + ", \"zone3\""; 353 | break; 354 | case 4: 355 | tmpString = tmpString + ", \"zone4\""; 356 | break; 357 | } 358 | } 359 | json = "{\"group_id\":\"" + groupId + "\", \"zone\":[" + tmpString + "]}"; 360 | logger.trace("setClientInfo json: {}", json); 361 | httpResponse = setClientServerInfo(this.host, json, "setClientInfo"); 362 | httpResponse = startDistribution(mclinkSetupServer); 363 | } 364 | } 365 | } 366 | } 367 | updateMCLinkStatus(); 368 | break; 369 | case CHANNEL_RECALLSCENE: 370 | recallScene(command.toString(), zone, this.host); 371 | break; 372 | case CHANNEL_REPEAT: 373 | setRepeat(command.toString(), this.host); 374 | break; 375 | case CHANNEL_SHUFFLE: 376 | setShuffle(command.toString(), this.host); 377 | break; 378 | } // END Switch Channel 379 | } 380 | } 381 | 382 | @Override 383 | public void initialize() { 384 | String localHost = ""; 385 | thingLabel = thing.getLabel(); 386 | updateStatus(ThingStatus.UNKNOWN); 387 | localHost = getThing().getConfiguration().get("host").toString(); 388 | this.host = localHost; 389 | if (!"".equals(this.host)) { 390 | zoneNum = getNumberOfZones(this.host); 391 | logger.trace("Zones found: {} - {}", zoneNum, thingLabel); 392 | 393 | if (zoneNum > 0) { 394 | refreshOnStartup(); 395 | generalHousekeepingTask = scheduler.scheduleWithFixedDelay(this::generalHousekeeping, 5, 300, 396 | TimeUnit.SECONDS); 397 | updateStatus(ThingStatus.ONLINE); 398 | } else { 399 | updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "No host found"); 400 | } 401 | } 402 | } 403 | 404 | private void generalHousekeeping() { 405 | thingLabel = thing.getLabel(); 406 | logger.trace("YXC - Start Keep Alive UDP events (5 minutes - {}) ", thingLabel); 407 | keepUdpEventsAlive(this.host); 408 | fillOptionsForMCLink(); 409 | updateMCLinkStatus(); 410 | } 411 | 412 | private void refreshOnStartup() { 413 | for (int i = 1; i <= zoneNum; i++) { 414 | switch (i) { 415 | case 1: 416 | createChannels("main"); 417 | updateStatusZone("main"); 418 | break; 419 | case 2: 420 | createChannels("zone2"); 421 | updateStatusZone("zone2"); 422 | break; 423 | case 3: 424 | createChannels("zone3"); 425 | updateStatusZone("zone3"); 426 | break; 427 | case 4: 428 | createChannels("zone4"); 429 | updateStatusZone("zone4"); 430 | break; 431 | } 432 | } 433 | updatePresets(0); 434 | updateNetUSBPlayer(); 435 | fillOptionsForMCLink(); 436 | updateMCLinkStatus(); 437 | } 438 | 439 | @Override 440 | public void dispose() { 441 | ScheduledFuture localGeneralHousekeepingTask = generalHousekeepingTask; 442 | if (localGeneralHousekeepingTask != null) { 443 | localGeneralHousekeepingTask.cancel(true); 444 | } 445 | } 446 | 447 | // Various functions 448 | 449 | private void createChannels(String zone) { 450 | createChannel(zone, CHANNEL_POWER, CHANNEL_TYPE_UID_POWER, "Switch"); 451 | createChannel(zone, CHANNEL_MUTE, CHANNEL_TYPE_UID_MUTE, "Switch"); 452 | createChannel(zone, CHANNEL_VOLUME, CHANNEL_TYPE_UID_VOLUME, "Dimmer"); 453 | createChannel(zone, CHANNEL_VOLUMEABS, CHANNEL_TYPE_UID_VOLUMEABS, "Number"); 454 | createChannel(zone, CHANNEL_INPUT, CHANNEL_TYPE_UID_INPUT, "String"); 455 | createChannel(zone, CHANNEL_SOUNDPROGRAM, CHANNEL_TYPE_UID_SOUNDPROGRAM, "String"); 456 | createChannel(zone, CHANNEL_SLEEP, CHANNEL_TYPE_UID_SLEEP, "Number"); 457 | createChannel(zone, CHANNEL_SELECTPRESET, CHANNEL_TYPE_UID_SELECTPRESET, "String"); 458 | createChannel(zone, CHANNEL_RECALLSCENE, CHANNEL_TYPE_UID_RECALLSCENE, "Number"); 459 | createChannel(zone, CHANNEL_MCLINKSTATUS, CHANNEL_TYPE_UID_MCLINKSTATUS, "String"); 460 | } 461 | 462 | private void createChannel(String zone, String channel, ChannelTypeUID channelTypeUID, String itemType) { 463 | ChannelUID channelToCheck = new ChannelUID(thing.getUID(), zone, channel); 464 | if (thing.getChannel(channelToCheck) == null) { 465 | ThingBuilder thingBuilder = editThing(); 466 | Channel testchannel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), zone, channel), itemType) 467 | .withType(channelTypeUID).build(); 468 | thingBuilder.withChannel(testchannel); 469 | updateThing(thingBuilder.build()); 470 | } 471 | } 472 | 473 | private void powerOffCleanup() { 474 | ChannelUID channel; 475 | channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST); 476 | updateState(channel, StringType.valueOf("-")); 477 | channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK); 478 | updateState(channel, StringType.valueOf("-")); 479 | channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM); 480 | updateState(channel, StringType.valueOf("-")); 481 | } 482 | 483 | public void processUDPEvent(String json, String trackingID) { 484 | logger.trace("UDP package: {} (Tracking: {})", json, trackingID); 485 | @Nullable 486 | UdpMessage targetObject = gson.fromJson(json, UdpMessage.class); 487 | if (targetObject != null) { 488 | if (Objects.nonNull(targetObject.getMain())) { 489 | updateStateFromUDPEvent("main", targetObject); 490 | } 491 | if (Objects.nonNull(targetObject.getZone2())) { 492 | updateStateFromUDPEvent("zone2", targetObject); 493 | } 494 | if (Objects.nonNull(targetObject.getZone3())) { 495 | updateStateFromUDPEvent("zone3", targetObject); 496 | } 497 | if (Objects.nonNull(targetObject.getZone4())) { 498 | updateStateFromUDPEvent("zone4", targetObject); 499 | } 500 | if (Objects.nonNull(targetObject.getNetUSB())) { 501 | updateStateFromUDPEvent("netusb", targetObject); 502 | } 503 | if (Objects.nonNull(targetObject.getDist())) { 504 | updateStateFromUDPEvent("dist", targetObject); 505 | } 506 | } 507 | } 508 | 509 | private void updateStateFromUDPEvent(String zoneToUpdate, UdpMessage targetObject) { 510 | ChannelUID channel; 511 | String playInfoUpdated = ""; 512 | String statusUpdated = ""; 513 | String powerState = ""; 514 | String muteState = ""; 515 | String inputState = ""; 516 | int volumeState = 0; 517 | int presetNumber = 0; 518 | int playTime = 0; 519 | String distInfoUpdated = ""; 520 | logger.trace("Handling UDP for {}", zoneToUpdate); 521 | switch (zoneToUpdate) { 522 | case "main": 523 | powerState = targetObject.getMain().getPower(); 524 | muteState = targetObject.getMain().getMute(); 525 | inputState = targetObject.getMain().getInput(); 526 | volumeState = targetObject.getMain().getVolume(); 527 | statusUpdated = targetObject.getMain().getstatusUpdated(); 528 | break; 529 | case "zone2": 530 | powerState = targetObject.getZone2().getPower(); 531 | muteState = targetObject.getZone2().getMute(); 532 | inputState = targetObject.getZone2().getInput(); 533 | volumeState = targetObject.getZone2().getVolume(); 534 | statusUpdated = targetObject.getZone2().getstatusUpdated(); 535 | break; 536 | case "zone3": 537 | powerState = targetObject.getZone3().getPower(); 538 | muteState = targetObject.getZone3().getMute(); 539 | inputState = targetObject.getZone3().getInput(); 540 | volumeState = targetObject.getZone3().getVolume(); 541 | statusUpdated = targetObject.getZone3().getstatusUpdated(); 542 | break; 543 | case "zone4": 544 | powerState = targetObject.getZone4().getPower(); 545 | muteState = targetObject.getZone4().getMute(); 546 | inputState = targetObject.getZone4().getInput(); 547 | volumeState = targetObject.getZone4().getVolume(); 548 | statusUpdated = targetObject.getZone4().getstatusUpdated(); 549 | break; 550 | case "netusb": 551 | if (Objects.isNull(targetObject.getNetUSB().getPresetControl())) { 552 | presetNumber = 0; 553 | } else { 554 | presetNumber = targetObject.getNetUSB().getPresetControl().getNum(); 555 | } 556 | playInfoUpdated = targetObject.getNetUSB().getPlayInfoUpdated(); 557 | playTime = targetObject.getNetUSB().getPlayTime(); 558 | // totalTime is not in UDP event 559 | break; 560 | case "dist": 561 | distInfoUpdated = targetObject.getDist().getDistInfoUpdated(); 562 | break; 563 | } 564 | 565 | if (!powerState.isEmpty()) { 566 | channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_POWER); 567 | if ("on".equals(powerState)) { 568 | updateState(channel, OnOffType.ON); 569 | } else if ("standby".equals(powerState)) { 570 | updateState(channel, OnOffType.OFF); 571 | powerOffCleanup(); 572 | } 573 | } 574 | 575 | if (!muteState.isEmpty()) { 576 | channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_MUTE); 577 | if ("true".equals(muteState)) { 578 | updateState(channel, OnOffType.ON); 579 | } else if ("false".equals(muteState)) { 580 | updateState(channel, OnOffType.OFF); 581 | } 582 | } 583 | 584 | if (!inputState.isEmpty()) { 585 | channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_INPUT); 586 | updateState(channel, StringType.valueOf(inputState)); 587 | } 588 | 589 | if (volumeState != 0) { 590 | channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUME); 591 | updateState(channel, new PercentType((volumeState * 100) / maxVolumeState)); 592 | channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUMEABS); 593 | updateState(channel, new DecimalType(volumeState)); 594 | } 595 | 596 | if (presetNumber != 0) { 597 | logger.trace("Preset detected: {}", presetNumber); 598 | updatePresets(presetNumber); 599 | } 600 | 601 | if ("true".equals(playInfoUpdated)) { 602 | updateNetUSBPlayer(); 603 | } 604 | 605 | if (!statusUpdated.isEmpty()) { 606 | updateStatusZone(zoneToUpdate); 607 | } 608 | if (playTime != 0) { 609 | channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME); 610 | updateState(channel, StringType.valueOf(String.valueOf(playTime))); 611 | } 612 | if ("true".equals(distInfoUpdated)) { 613 | updateMCLinkStatus(); 614 | } 615 | } 616 | 617 | private void updateStatusZone(String zoneToUpdate) { 618 | String localZone = ""; 619 | tmpString = getStatus(this.host, zoneToUpdate); 620 | @Nullable 621 | Status targetObject = gson.fromJson(tmpString, Status.class); 622 | if (targetObject != null) { 623 | String responseCode = targetObject.getResponseCode(); 624 | String powerState = targetObject.getPower(); 625 | String muteState = targetObject.getMute(); 626 | volumeState = targetObject.getVolume(); 627 | maxVolumeState = targetObject.getMaxVolume(); 628 | inputState = targetObject.getInput(); 629 | soundProgramState = targetObject.getSoundProgram(); 630 | sleepState = targetObject.getSleep(); 631 | 632 | logger.trace("{} - Response: {}", zoneToUpdate, responseCode); 633 | logger.trace("{} - Power: {}", zoneToUpdate, powerState); 634 | logger.trace("{} - Mute: {}", zoneToUpdate, muteState); 635 | logger.trace("{} - Volume: {}", zoneToUpdate, volumeState); 636 | logger.trace("{} - Max Volume: {}", zoneToUpdate, maxVolumeState); 637 | logger.trace("{} - Input: {}", zoneToUpdate, inputState); 638 | logger.trace("{} - Soundprogram: {}", zoneToUpdate, soundProgramState); 639 | logger.trace("{} - Sleep: {}", zoneToUpdate, sleepState); 640 | 641 | switch (responseCode) { 642 | case "0": 643 | for (Channel channel : getThing().getChannels()) { 644 | ChannelUID channelUID = channel.getUID(); 645 | channelWithoutGroup = channelUID.getIdWithoutGroup(); 646 | localZone = channelUID.getGroupId(); 647 | if (localZone != null) { 648 | if (isLinked(channelUID)) { 649 | switch (channelWithoutGroup) { 650 | case CHANNEL_POWER: 651 | if ("on".equals(powerState)) { 652 | if (localZone.equals(zoneToUpdate)) { 653 | updateState(channelUID, OnOffType.ON); 654 | } 655 | } else if ("standby".equals(powerState)) { 656 | if (localZone.equals(zoneToUpdate)) { 657 | updateState(channelUID, OnOffType.OFF); 658 | } 659 | } 660 | break; 661 | case CHANNEL_MUTE: 662 | if ("true".equals(muteState)) { 663 | if (localZone.equals(zoneToUpdate)) { 664 | updateState(channelUID, OnOffType.ON); 665 | } 666 | } else if ("false".equals(muteState)) { 667 | if (localZone.equals(zoneToUpdate)) { 668 | updateState(channelUID, OnOffType.OFF); 669 | } 670 | } 671 | break; 672 | case CHANNEL_VOLUME: 673 | if (localZone.equals(zoneToUpdate)) { 674 | updateState(channelUID, 675 | new PercentType((volumeState * 100) / maxVolumeState)); 676 | } 677 | break; 678 | case CHANNEL_VOLUMEABS: 679 | if (localZone.equals(zoneToUpdate)) { 680 | updateState(channelUID, new DecimalType(volumeState)); 681 | } 682 | break; 683 | case CHANNEL_INPUT: 684 | if (localZone.equals(zoneToUpdate)) { 685 | updateState(channelUID, StringType.valueOf(inputState)); 686 | } 687 | break; 688 | case CHANNEL_SOUNDPROGRAM: 689 | if (localZone.equals(zoneToUpdate)) { 690 | updateState(channelUID, StringType.valueOf(soundProgramState)); 691 | } 692 | break; 693 | case CHANNEL_SLEEP: 694 | if (localZone.equals(zoneToUpdate)) { 695 | updateState(channelUID, new DecimalType(sleepState)); 696 | } 697 | break; 698 | } // END switch (channelWithoutGroup) 699 | } // END IsLinked 700 | } 701 | } 702 | break; 703 | case "999": 704 | logger.trace("Nothing to do! - {} ({})", thingLabel, zoneToUpdate); 705 | break; 706 | } 707 | } 708 | } 709 | 710 | private void updatePresets(int value) { 711 | String inputText = ""; 712 | int presetCounter = 0; 713 | int currentPreset = 0; 714 | tmpString = getPresetInfo(this.host); 715 | 716 | PresetInfo presetinfo = gson.fromJson(tmpString, PresetInfo.class); 717 | if (presetinfo != null) { 718 | String responseCode = presetinfo.getResponseCode(); 719 | if ("0".equals(responseCode)) { 720 | List optionsPresets = new ArrayList<>(); 721 | inputText = getLastInput(); 722 | if (inputText != null) { 723 | for (JsonElement pr : presetinfo.getPresetInfo()) { 724 | presetCounter = presetCounter + 1; 725 | JsonObject presetObject = pr.getAsJsonObject(); 726 | String text = presetObject.get("text").getAsString(); 727 | if (!"".equals(text)) { 728 | optionsPresets.add(new StateOption(String.valueOf(presetCounter), 729 | "#" + String.valueOf(presetCounter) + " " + text)); 730 | if (inputText.equals(text)) { 731 | currentPreset = presetCounter; 732 | } 733 | } 734 | } 735 | } 736 | if (value != 0) { 737 | currentPreset = value; 738 | } 739 | for (Channel channel : getThing().getChannels()) { 740 | ChannelUID channelUID = channel.getUID(); 741 | channelWithoutGroup = channelUID.getIdWithoutGroup(); 742 | if (isLinked(channelUID)) { 743 | switch (channelWithoutGroup) { 744 | case CHANNEL_SELECTPRESET: 745 | stateDescriptionProvider.setStateOptions(channelUID, optionsPresets); 746 | updateState(channelUID, StringType.valueOf(String.valueOf(currentPreset))); 747 | break; 748 | } 749 | } 750 | } 751 | } 752 | } 753 | } 754 | 755 | private void updateNetUSBPlayer() { 756 | tmpString = getPlayInfo(this.host); 757 | 758 | @Nullable 759 | PlayInfo targetObject = gson.fromJson(tmpString, PlayInfo.class); 760 | if (targetObject != null) { 761 | String responseCode = targetObject.getResponseCode(); 762 | String playbackState = targetObject.getPlayback(); 763 | artistState = targetObject.getArtist(); 764 | trackState = targetObject.getTrack(); 765 | albumState = targetObject.getAlbum(); 766 | String albumArtUrlState = targetObject.getAlbumArtUrl(); 767 | repeatState = targetObject.getRepeat(); 768 | shuffleState = targetObject.getShuffle(); 769 | playTimeState = targetObject.getPlayTime(); 770 | totalTimeState = targetObject.getTotalTime(); 771 | 772 | if ("0".equals(responseCode)) { 773 | ChannelUID testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYER); 774 | switch (playbackState) { 775 | case "play": 776 | updateState(testchannel, PlayPauseType.PLAY); 777 | break; 778 | case "stop": 779 | updateState(testchannel, PlayPauseType.PAUSE); 780 | break; 781 | case "pause": 782 | updateState(testchannel, PlayPauseType.PAUSE); 783 | break; 784 | case "fast_reverse": 785 | updateState(testchannel, RewindFastforwardType.REWIND); 786 | break; 787 | case "fast_forward": 788 | updateState(testchannel, RewindFastforwardType.FASTFORWARD); 789 | break; 790 | } 791 | testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST); 792 | updateState(testchannel, StringType.valueOf(artistState)); 793 | testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK); 794 | updateState(testchannel, StringType.valueOf(trackState)); 795 | testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM); 796 | updateState(testchannel, StringType.valueOf(albumState)); 797 | testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUMART); 798 | if (!"".equals(albumArtUrlState)) { 799 | albumArtUrlState = HTTP + this.host + albumArtUrlState; 800 | } 801 | updateState(testchannel, StringType.valueOf(albumArtUrlState)); 802 | testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_REPEAT); 803 | updateState(testchannel, StringType.valueOf(repeatState)); 804 | testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_SHUFFLE); 805 | updateState(testchannel, StringType.valueOf(shuffleState)); 806 | testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME); 807 | updateState(testchannel, StringType.valueOf(String.valueOf(playTimeState))); 808 | testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TOTALTIME); 809 | updateState(testchannel, StringType.valueOf(String.valueOf(totalTimeState))); 810 | } 811 | } 812 | } 813 | 814 | private @Nullable String getLastInput() { 815 | String text = ""; 816 | tmpString = getRecentInfo(this.host); 817 | RecentInfo recentinfo = gson.fromJson(tmpString, RecentInfo.class); 818 | if (recentinfo != null) { 819 | String responseCode = recentinfo.getResponseCode(); 820 | if ("0".equals(responseCode)) { 821 | for (JsonElement ri : recentinfo.getRecentInfo()) { 822 | JsonObject recentObject = ri.getAsJsonObject(); 823 | text = recentObject.get("text").getAsString(); 824 | break; 825 | } 826 | } 827 | } 828 | return text; 829 | } 830 | 831 | private String connectedServer() { 832 | DistributionInfo distributioninfo = new DistributionInfo(); 833 | Bridge bridge = getBridge(); 834 | String remotehost = ""; 835 | String result = ""; 836 | String localHost = ""; 837 | if (bridge != null) { 838 | for (Thing thing : bridge.getThings()) { 839 | remotehost = thing.getConfiguration().get("host").toString(); 840 | tmpString = getDistributionInfo(remotehost); 841 | distributioninfo = gson.fromJson(tmpString, DistributionInfo.class); 842 | if (distributioninfo != null) { 843 | String localRole = distributioninfo.getRole(); 844 | if ("server".equals(localRole)) { 845 | for (JsonElement ip : distributioninfo.getClientList()) { 846 | JsonObject clientObject = ip.getAsJsonObject(); 847 | localHost = getThing().getConfiguration().get("host").toString(); 848 | if (localHost.equals(clientObject.get("ip_address").getAsString())) { 849 | result = remotehost; 850 | break; 851 | } 852 | } 853 | } 854 | } 855 | } 856 | } 857 | return result; 858 | } 859 | 860 | private void fillOptionsForMCLink() { 861 | Bridge bridge = getBridge(); 862 | String host = ""; 863 | String label = ""; 864 | int zonesPerHost = 1; 865 | int clients = 0; 866 | tmpString = getDistributionInfo(this.host); 867 | DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class); 868 | if (targetObject != null) { 869 | clients = targetObject.getClientList().size(); 870 | } 871 | 872 | List options = new ArrayList<>(); 873 | // first add 3 options for MC Link 874 | options.add(new StateOption("", "Standalone")); 875 | options.add(new StateOption("server", "Server: " + clients + " clients")); 876 | options.add(new StateOption("client", "Client")); 877 | 878 | if (bridge != null) { 879 | for (Thing thing : bridge.getThings()) { 880 | label = thing.getLabel(); 881 | host = thing.getConfiguration().get("host").toString(); 882 | logger.trace("Thing found on Bridge: {} - {}", label, host); 883 | zonesPerHost = getNumberOfZones(host); 884 | for (int i = 1; i <= zonesPerHost; i++) { 885 | switch (i) { 886 | case 1: 887 | options.add(new StateOption(host + "***main", label + " - main (" + host + ")")); 888 | break; 889 | case 2: 890 | options.add(new StateOption(host + "***zone2", label + " - zone2 (" + host + ")")); 891 | break; 892 | case 3: 893 | options.add(new StateOption(host + "***zone3", label + " - zone3 (" + host + ")")); 894 | break; 895 | case 4: 896 | options.add(new StateOption(host + "***zone4", label + " - zone4 (" + host + ")")); 897 | break; 898 | } 899 | } 900 | 901 | } 902 | } 903 | // for each zone of the device, set all the possible combinations 904 | ChannelUID testchannel; 905 | for (int i = 1; i <= zoneNum; i++) { 906 | switch (i) { 907 | case 1: 908 | testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS); 909 | if (isLinked(testchannel)) { 910 | stateDescriptionProvider.setStateOptions(testchannel, options); 911 | } 912 | break; 913 | case 2: 914 | testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS); 915 | if (isLinked(testchannel)) { 916 | stateDescriptionProvider.setStateOptions(testchannel, options); 917 | } 918 | break; 919 | case 3: 920 | testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS); 921 | if (isLinked(testchannel)) { 922 | stateDescriptionProvider.setStateOptions(testchannel, options); 923 | } 924 | break; 925 | case 4: 926 | testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS); 927 | if (isLinked(testchannel)) { 928 | stateDescriptionProvider.setStateOptions(testchannel, options); 929 | } 930 | break; 931 | } 932 | } 933 | } 934 | 935 | private String generateGroupId() { 936 | return UUID.randomUUID().toString().replace("-", "").substring(0, 32); 937 | } 938 | 939 | private int getNumberOfZones(@Nullable String host) { 940 | int numberOfZones = 0; 941 | tmpString = getFeatures(host); 942 | @Nullable 943 | Features targetObject = gson.fromJson(tmpString, Features.class); 944 | if (targetObject != null) { 945 | responseCode = targetObject.getResponseCode(); 946 | if ("0".equals(responseCode)) { 947 | numberOfZones = targetObject.getSystem().getZoneNum(); 948 | } 949 | } 950 | return numberOfZones; 951 | } 952 | 953 | public @Nullable String getDeviceId() { 954 | tmpString = getDeviceInfo(this.host); 955 | String localValueToCheck = ""; 956 | @Nullable 957 | DeviceInfo targetObject = gson.fromJson(tmpString, DeviceInfo.class); 958 | if (targetObject != null) { 959 | localValueToCheck = targetObject.getDeviceId(); 960 | } 961 | return localValueToCheck; 962 | } 963 | 964 | private void setVolumeLinkedDevice(int value, @Nullable String zone, String host) { 965 | logger.trace("setVolumeLinkedDevice: {}", host); 966 | int zoneNumLinkedDevice = getNumberOfZones(host); 967 | int maxVolumeLinkedDevice = 0; 968 | @Nullable 969 | Status targetObject = new Status(); 970 | int newVolume = 0; 971 | for (int i = 1; i <= zoneNumLinkedDevice; i++) { 972 | switch (i) { 973 | case 1: 974 | tmpString = getStatus(host, "main"); 975 | targetObject = gson.fromJson(tmpString, Status.class); 976 | if (targetObject != null) { 977 | responseCode = targetObject.getResponseCode(); 978 | maxVolumeLinkedDevice = targetObject.getMaxVolume(); 979 | newVolume = maxVolumeLinkedDevice * value / 100; 980 | setVolume(newVolume, "main", host); 981 | } 982 | break; 983 | case 2: 984 | tmpString = getStatus(host, "zone2"); 985 | targetObject = gson.fromJson(tmpString, Status.class); 986 | if (targetObject != null) { 987 | responseCode = targetObject.getResponseCode(); 988 | maxVolumeLinkedDevice = targetObject.getMaxVolume(); 989 | newVolume = maxVolumeLinkedDevice * value / 100; 990 | setVolume(newVolume, "zone2", host); 991 | } 992 | break; 993 | case 3: 994 | tmpString = getStatus(host, "zone3"); 995 | targetObject = gson.fromJson(tmpString, Status.class); 996 | if (targetObject != null) { 997 | responseCode = targetObject.getResponseCode(); 998 | maxVolumeLinkedDevice = targetObject.getMaxVolume(); 999 | newVolume = maxVolumeLinkedDevice * value / 100; 1000 | setVolume(newVolume, "zone3", host); 1001 | } 1002 | break; 1003 | case 4: 1004 | tmpString = getStatus(host, "zone4"); 1005 | targetObject = gson.fromJson(tmpString, Status.class); 1006 | if (targetObject != null) { 1007 | responseCode = targetObject.getResponseCode(); 1008 | maxVolumeLinkedDevice = targetObject.getMaxVolume(); 1009 | newVolume = maxVolumeLinkedDevice * value / 100; 1010 | setVolume(newVolume, "zone4", host); 1011 | } 1012 | break; 1013 | } 1014 | } 1015 | } 1016 | 1017 | public void updateMCLinkStatus() { 1018 | tmpString = getDistributionInfo(this.host); 1019 | @Nullable 1020 | DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class); 1021 | if (targetObject != null) { 1022 | String localRole = targetObject.getRole(); 1023 | groupId = targetObject.getGroupId(); 1024 | switch (localRole) { 1025 | case "none": 1026 | setMCLinkToStandalone(); 1027 | break; 1028 | case "server": 1029 | setMCLinkToServer(); 1030 | break; 1031 | case "client": 1032 | setMCLinkToClient(); 1033 | break; 1034 | } 1035 | } 1036 | } 1037 | 1038 | private void setMCLinkToStandalone() { 1039 | ChannelUID testchannel; 1040 | for (int i = 1; i <= zoneNum; i++) { 1041 | switch (i) { 1042 | case 1: 1043 | testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS); 1044 | updateState(testchannel, StringType.valueOf("")); 1045 | break; 1046 | case 2: 1047 | testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS); 1048 | updateState(testchannel, StringType.valueOf("")); 1049 | break; 1050 | case 3: 1051 | testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS); 1052 | updateState(testchannel, StringType.valueOf("")); 1053 | break; 1054 | case 4: 1055 | testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS); 1056 | updateState(testchannel, StringType.valueOf("")); 1057 | break; 1058 | } 1059 | } 1060 | } 1061 | 1062 | private void setMCLinkToClient() { 1063 | ChannelUID testchannel; 1064 | for (int i = 1; i <= zoneNum; i++) { 1065 | switch (i) { 1066 | case 1: 1067 | testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS); 1068 | updateState(testchannel, StringType.valueOf("client")); 1069 | break; 1070 | case 2: 1071 | testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS); 1072 | updateState(testchannel, StringType.valueOf("client")); 1073 | break; 1074 | case 3: 1075 | testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS); 1076 | updateState(testchannel, StringType.valueOf("client")); 1077 | break; 1078 | case 4: 1079 | testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS); 1080 | updateState(testchannel, StringType.valueOf("client")); 1081 | break; 1082 | } 1083 | } 1084 | } 1085 | 1086 | private void setMCLinkToServer() { 1087 | ChannelUID testchannel; 1088 | for (int i = 1; i <= zoneNum; i++) { 1089 | switch (i) { 1090 | case 1: 1091 | testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS); 1092 | updateState(testchannel, StringType.valueOf("server")); 1093 | break; 1094 | case 2: 1095 | testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS); 1096 | updateState(testchannel, StringType.valueOf("server")); 1097 | break; 1098 | case 3: 1099 | testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS); 1100 | updateState(testchannel, StringType.valueOf("server")); 1101 | break; 1102 | case 4: 1103 | testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS); 1104 | updateState(testchannel, StringType.valueOf("server")); 1105 | break; 1106 | } 1107 | } 1108 | } 1109 | 1110 | private String makeRequest(@Nullable String topicAVR, String url) { 1111 | String response = ""; 1112 | try { 1113 | response = HttpUtil.executeUrl("GET", HTTP + url, LONG_CONNECTION_TIMEOUT_MILLISEC); 1114 | logger.trace("{} - {}", topicAVR, response); 1115 | return response; 1116 | } catch (IOException e) { 1117 | logger.trace("IO Exception - {} - {}", topicAVR, e.getMessage()); 1118 | return "{\"response_code\":\"999\"}"; 1119 | } 1120 | } 1121 | // End Various functions 1122 | 1123 | // API calls to AVR 1124 | 1125 | // Start Zone Related 1126 | 1127 | private @Nullable String getStatus(@Nullable String host, String zone) { 1128 | return makeRequest("Status", host + YAMAHA_EXTENDED_CONTROL + zone + "/getStatus"); 1129 | } 1130 | 1131 | private @Nullable String setPower(String value, @Nullable String zone, @Nullable String host) { 1132 | return makeRequest("Power", host + YAMAHA_EXTENDED_CONTROL + zone + "/setPower?power=" + value); 1133 | } 1134 | 1135 | private @Nullable String setMute(String value, @Nullable String zone, @Nullable String host) { 1136 | return makeRequest("Mute", host + YAMAHA_EXTENDED_CONTROL + zone + "/setMute?enable=" + value); 1137 | } 1138 | 1139 | private @Nullable String setVolume(int value, @Nullable String zone, @Nullable String host) { 1140 | return makeRequest("Volume", host + YAMAHA_EXTENDED_CONTROL + zone + "/setVolume?volume=" + value); 1141 | } 1142 | 1143 | private @Nullable String setInput(String value, @Nullable String zone, @Nullable String host) { 1144 | return makeRequest("setInput", host + YAMAHA_EXTENDED_CONTROL + zone + "/setInput?input=" + value); 1145 | } 1146 | 1147 | private @Nullable String setSoundProgram(String value, @Nullable String zone, @Nullable String host) { 1148 | return makeRequest("setSoundProgram", 1149 | host + YAMAHA_EXTENDED_CONTROL + zone + "/setSoundProgram?program=" + value); 1150 | } 1151 | 1152 | private @Nullable String setPreset(String value, @Nullable String zone, @Nullable String host) { 1153 | return makeRequest("setPreset", 1154 | host + YAMAHA_EXTENDED_CONTROL + "netusb/recallPreset?zone=" + zone + "&num=" + value); 1155 | } 1156 | 1157 | private @Nullable String setSleep(String value, @Nullable String zone, @Nullable String host) { 1158 | return makeRequest("setSleep", host + YAMAHA_EXTENDED_CONTROL + zone + "/setSleep?sleep=" + value); 1159 | } 1160 | 1161 | private @Nullable String recallScene(String value, @Nullable String zone, @Nullable String host) { 1162 | return makeRequest("recallScene", host + YAMAHA_EXTENDED_CONTROL + zone + "/recallScene?num=" + value); 1163 | } 1164 | // End Zone Related 1165 | 1166 | // Start Net Radio/USB Related 1167 | 1168 | private @Nullable String getPresetInfo(@Nullable String host) { 1169 | return makeRequest("PresetInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPresetInfo"); 1170 | } 1171 | 1172 | private @Nullable String getRecentInfo(@Nullable String host) { 1173 | return makeRequest("RecentInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getRecentInfo"); 1174 | } 1175 | 1176 | private @Nullable String getPlayInfo(@Nullable String host) { 1177 | return makeRequest("PlayInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo"); 1178 | } 1179 | 1180 | private @Nullable String setPlayback(String value, @Nullable String host) { 1181 | return makeRequest("Playback", host + YAMAHA_EXTENDED_CONTROL + "netusb/setPlayback?playback=" + value); 1182 | } 1183 | 1184 | private @Nullable String setRepeat(String value, @Nullable String host) { 1185 | return makeRequest("Repeat", host + YAMAHA_EXTENDED_CONTROL + "netusb/setRepeat?mode=" + value); 1186 | } 1187 | 1188 | private @Nullable String setShuffle(String value, @Nullable String host) { 1189 | return makeRequest("Shuffle", host + YAMAHA_EXTENDED_CONTROL + "netusb/setShuffle?mode=" + value); 1190 | } 1191 | 1192 | // End Net Radio/USB Related 1193 | 1194 | // Start Music Cast API calls 1195 | private @Nullable String getDistributionInfo(@Nullable String host) { 1196 | return makeRequest("DistributionInfo", host + YAMAHA_EXTENDED_CONTROL + "dist/getDistributionInfo"); 1197 | } 1198 | 1199 | private @Nullable String setClientServerInfo(@Nullable String host, String json, String type) { 1200 | InputStream is = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); 1201 | try { 1202 | url = "http://" + host + YAMAHA_EXTENDED_CONTROL + "dist/" + type; 1203 | httpResponse = HttpUtil.executeUrl("POST", url, is, "", LONG_CONNECTION_TIMEOUT_MILLISEC); 1204 | logger.trace("MC Link/Unlink Client {}", httpResponse); 1205 | return httpResponse; 1206 | } catch (IOException e) { 1207 | logger.trace("IO Exception - {} - {}", type, e.getMessage()); 1208 | return "{\"response_code\":\"999\"}"; 1209 | } 1210 | } 1211 | 1212 | private @Nullable String startDistribution(@Nullable String host) { 1213 | Random ran = new Random(); 1214 | int nxt = ran.nextInt(200000); 1215 | return makeRequest("StartDistribution", host + YAMAHA_EXTENDED_CONTROL + "dist/startDistribution?num=" + nxt); 1216 | } 1217 | 1218 | // End Music Cast API calls 1219 | 1220 | // Start General/System API calls 1221 | 1222 | private @Nullable String getFeatures(@Nullable String host) { 1223 | return makeRequest("Features", host + YAMAHA_EXTENDED_CONTROL + "system/getFeatures"); 1224 | } 1225 | 1226 | private @Nullable String getDeviceInfo(@Nullable String host) { 1227 | return makeRequest("DeviceInfo", host + YAMAHA_EXTENDED_CONTROL + "system/getDeviceInfo"); 1228 | } 1229 | 1230 | private void keepUdpEventsAlive(@Nullable String host) { 1231 | Properties appProps = new Properties(); 1232 | appProps.setProperty("X-AppName", "MusicCast/1"); 1233 | appProps.setProperty("X-AppPort", "41100"); 1234 | try { 1235 | httpResponse = HttpUtil.executeUrl("GET", HTTP + host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo", 1236 | appProps, null, "", LONG_CONNECTION_TIMEOUT_MILLISEC); 1237 | // logger.trace("{}", httpResponse); 1238 | logger.trace("{} - {}", "UDP task", httpResponse); 1239 | } catch (IOException e) { 1240 | logger.trace("UDP refresh failed - {}", e.getMessage()); 1241 | } 1242 | } 1243 | // End General/System API calls 1244 | } 1245 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/yamahamusiccastHandlerFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal; 14 | 15 | import static org.openhab.binding.yamahamusiccast.internal.YamahaMusiccastBindingConstants.*; 16 | 17 | import java.util.Set; 18 | 19 | import org.eclipse.jdt.annotation.NonNullByDefault; 20 | import org.eclipse.jdt.annotation.Nullable; 21 | import org.openhab.core.thing.Bridge; 22 | import org.openhab.core.thing.Thing; 23 | import org.openhab.core.thing.ThingTypeUID; 24 | import org.openhab.core.thing.binding.BaseThingHandlerFactory; 25 | import org.openhab.core.thing.binding.ThingHandler; 26 | import org.openhab.core.thing.binding.ThingHandlerFactory; 27 | import org.osgi.service.component.annotations.Activate; 28 | import org.osgi.service.component.annotations.Component; 29 | import org.osgi.service.component.annotations.Reference; 30 | 31 | /** 32 | * The {@link YamahamusiccastHandlerFactory} is responsible for creating things and thing 33 | * handlers. 34 | * 35 | * @author Lennert Coopman - Initial contribution 36 | */ 37 | @NonNullByDefault 38 | @Component(configurationPid = "binding.yamahamusiccast", service = ThingHandlerFactory.class) 39 | public class YamahaMusiccastHandlerFactory extends BaseThingHandlerFactory { 40 | 41 | private static final Set SUPPORTED_THING_TYPES_UIDS = Set 42 | .of(YamahaMusiccastBindingConstants.THING_DEVICE, YamahaMusiccastBindingConstants.THING_TYPE_BRIDGE); 43 | 44 | private final YamahaMusiccastStateDescriptionProvider stateDescriptionProvider; 45 | 46 | @Activate 47 | public YamahaMusiccastHandlerFactory(@Reference YamahaMusiccastStateDescriptionProvider stateDescriptionProvider) { 48 | this.stateDescriptionProvider = stateDescriptionProvider; 49 | } 50 | 51 | @Override 52 | public boolean supportsThingType(ThingTypeUID thingTypeUID) { 53 | return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); 54 | } 55 | 56 | @Override 57 | protected @Nullable ThingHandler createHandler(Thing thing) { 58 | ThingTypeUID thingTypeUID = thing.getThingTypeUID(); 59 | 60 | if (thingTypeUID.equals(THING_TYPE_BRIDGE)) { 61 | return new YamahaMusiccastBridgeHandler((Bridge) thing); 62 | } else if (THING_DEVICE.equals(thingTypeUID)) { 63 | return new YamahaMusiccastHandler(thing, stateDescriptionProvider); 64 | } 65 | return null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/yamahamusiccast/internal/yamahamusiccastStateDescriptionProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2021 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.yamahamusiccast.internal; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; 17 | import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; 18 | import org.openhab.core.thing.type.DynamicStateDescriptionProvider; 19 | import org.osgi.service.component.annotations.Component; 20 | import org.osgi.service.component.annotations.Reference; 21 | 22 | /** 23 | * The {@link YamahaMusiccastStateDescriptionProvider} is responsible for handling the state options of a channel. 24 | * 25 | * @author Lennert Coopman - Initial contribution 26 | */ 27 | @Component(service = { DynamicStateDescriptionProvider.class, YamahaMusiccastStateDescriptionProvider.class }) 28 | @NonNullByDefault 29 | public class YamahaMusiccastStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { 30 | 31 | @Reference 32 | protected void setChannelTypeI18nLocalizationService( 33 | final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { 34 | this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; 35 | } 36 | 37 | protected void unsetChannelTypeI18nLocalizationService( 38 | final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { 39 | this.channelTypeI18nLocalizationService = null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/OH-INF/binding/binding.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | Yamaha Musiccast Binding 7 | This is the binding for Yamaha Musiccast 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/OH-INF/thing/bridge.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | Virtual Bridge to receive updates 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/OH-INF/thing/thing-types.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Your Yamaha model with MusicCast functionality 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | network-address 27 | The IP address of the AVR to control. 28 | 29 | 30 | 31 | Sync Volume across linked Music Cast models 32 | true 33 | 34 | 35 | 36 | Default value for client when MC Link is broken 37 | net_radio 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Number 79 | 80 | Volume channel - Absolute value 81 | 82 | 83 | String 84 | 85 | Input channel 86 | 87 | 88 | String 89 | 90 | SoundProgram channel 91 | 92 | 93 | String 94 | 95 | Select Net Radio/USB Preset channel 96 | 97 | 98 | Player 99 | 100 | Player for Net Radio/USB channel 101 | 102 | 103 | Number 104 | 105 | Sleep Time in minutes 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | Number 118 | 119 | Scene selection (if available) 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | String 135 | 136 | Artist 137 | 138 | 139 | String 140 | 141 | Track 142 | 143 | 144 | String 145 | 146 | Album 147 | 148 | 149 | Image 150 | 151 | Album Art 152 | 153 | 154 | String 155 | 156 | Repeat mode 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | String 167 | 168 | Shuffle mode 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | String 180 | 181 | MusicCast Status 182 | 183 | 184 | Number:Time 185 | 186 | Play Time 187 | 188 | 189 | String 190 | 191 | Total Time 192 | 193 | 194 | -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastBindingConstants.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastBindingConstants.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastBridgeHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastBridgeHandler.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastConfiguration.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastConfiguration.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastHandler.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastHandlerFactory.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastHandlerFactory.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastStateDescriptionProvider.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/YamahaMusiccastStateDescriptionProvider.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/DeviceInfo.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/DeviceInfo.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/DistributionInfo$ClientList.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/DistributionInfo$ClientList.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/DistributionInfo.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/DistributionInfo.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/Features$System.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/Features$System.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/Features.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/Features.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/PlayInfo.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/PlayInfo.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/PresetInfo.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/PresetInfo.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/RecentInfo.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/RecentInfo.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/Response.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/Response.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/Status.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/Status.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage$Dist.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage$Dist.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage$NetUSB.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage$NetUSB.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage$PresetControl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage$PresetControl.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage$Zone.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage$Zone.class -------------------------------------------------------------------------------- /target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/classes/org/openhab/binding/yamahamusiccast/internal/dto/UdpMessage.class -------------------------------------------------------------------------------- /target/org.openhab.binding.yamahamusiccast-3.1.0-v0.79.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/org.openhab.binding.yamahamusiccast-3.1.0-v0.79.jar -------------------------------------------------------------------------------- /target/org.openhab.binding.yamahamusiccast-3.1.0-v0.80-test.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/org.openhab.binding.yamahamusiccast-3.1.0-v0.80-test.jar -------------------------------------------------------------------------------- /target/org.openhab.binding.yamahamusiccast-3.1.0-v0.81-beta.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/org.openhab.binding.yamahamusiccast-3.1.0-v0.81-beta.jar -------------------------------------------------------------------------------- /target/org.openhab.binding.yamahamusiccast-3.1.0-v0.82-beta.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/org.openhab.binding.yamahamusiccast-3.1.0-v0.82-beta.jar -------------------------------------------------------------------------------- /target/org.openhab.binding.yamahamusiccast-3.1.0-v0.83-beta.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/org.openhab.binding.yamahamusiccast-3.1.0-v0.83-beta.jar -------------------------------------------------------------------------------- /target/org.openhab.binding.yamahamusiccast-3.1.0-v0.84-beta.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/org.openhab.binding.yamahamusiccast-3.1.0-v0.84-beta.jar -------------------------------------------------------------------------------- /target/org.openhab.binding.yamahamusiccast-3.3.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coop-git/YamahaMusicCast/f1c89953dea014266c0295172d850301863da640/target/org.openhab.binding.yamahamusiccast-3.3.0.jar --------------------------------------------------------------------------------