2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.yamahamusiccast.internal;
15 import static org.openhab.binding.yamahamusiccast.internal.YamahaMusiccastBindingConstants.*;
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;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.yamahamusiccast.internal.dto.ActualVolume;
33 import org.openhab.binding.yamahamusiccast.internal.dto.DeviceInfo;
34 import org.openhab.binding.yamahamusiccast.internal.dto.DistributionInfo;
35 import org.openhab.binding.yamahamusiccast.internal.dto.Features;
36 import org.openhab.binding.yamahamusiccast.internal.dto.PlayInfo;
37 import org.openhab.binding.yamahamusiccast.internal.dto.PresetInfo;
38 import org.openhab.binding.yamahamusiccast.internal.dto.RecentInfo;
39 import org.openhab.binding.yamahamusiccast.internal.dto.Response;
40 import org.openhab.binding.yamahamusiccast.internal.dto.Status;
41 import org.openhab.binding.yamahamusiccast.internal.dto.UdpMessage;
42 import org.openhab.core.io.net.http.HttpUtil;
43 import org.openhab.core.library.types.DecimalType;
44 import org.openhab.core.library.types.NextPreviousType;
45 import org.openhab.core.library.types.OnOffType;
46 import org.openhab.core.library.types.PercentType;
47 import org.openhab.core.library.types.PlayPauseType;
48 import org.openhab.core.library.types.QuantityType;
49 import org.openhab.core.library.types.RewindFastforwardType;
50 import org.openhab.core.library.types.StringType;
51 import org.openhab.core.library.unit.Units;
52 import org.openhab.core.thing.Bridge;
53 import org.openhab.core.thing.Channel;
54 import org.openhab.core.thing.ChannelUID;
55 import org.openhab.core.thing.Thing;
56 import org.openhab.core.thing.ThingStatus;
57 import org.openhab.core.thing.ThingStatusDetail;
58 import org.openhab.core.thing.binding.BaseThingHandler;
59 import org.openhab.core.thing.binding.builder.ChannelBuilder;
60 import org.openhab.core.thing.binding.builder.ThingBuilder;
61 import org.openhab.core.thing.type.ChannelTypeUID;
62 import org.openhab.core.types.Command;
63 import org.openhab.core.types.RefreshType;
64 import org.openhab.core.types.StateOption;
65 import org.openhab.core.types.UnDefType;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
69 import com.google.gson.Gson;
70 import com.google.gson.JsonElement;
71 import com.google.gson.JsonObject;
74 * The {@link YamahaMusiccastHandler} is responsible for handling commands, which are
75 * sent to one of the channels.
77 * @author Lennert Coopman - Initial contribution
78 * @author Florian Hotze - Add volume in decibel
81 public class YamahaMusiccastHandler extends BaseThingHandler {
82 private Gson gson = new Gson();
83 private Logger logger = LoggerFactory.getLogger(YamahaMusiccastHandler.class);
84 private @Nullable ScheduledFuture<?> generalHousekeepingTask;
85 private @Nullable String httpResponse;
86 private @Nullable String tmpString = "";
87 private int volumePercent = 0;
88 private int volumeAbsValue = 0;
89 private @Nullable String responseCode = "";
90 private int volumeState = 0;
91 private int maxVolumeState = 0;
92 private @Nullable String inputState = "";
93 private @Nullable String soundProgramState = "";
94 private int sleepState = 0;
95 private @Nullable String artistState = "";
96 private @Nullable String trackState = "";
97 private @Nullable String albumState = "";
98 private @Nullable String repeatState = "";
99 private @Nullable String shuffleState = "";
100 private int playTimeState = 0;
101 private int totalTimeState = 0;
102 private @Nullable String zone = "main";
103 private String channelWithoutGroup = "";
104 private @Nullable String thingLabel = "";
105 private @Nullable String mclinkSetupServer = "";
106 private @Nullable String mclinkSetupZone = "";
107 private String url = "";
108 private String json = "";
109 private String action = "";
110 private int zoneNum = 0;
111 private @Nullable String groupId = "";
112 private @Nullable String host;
113 public @Nullable String deviceId = "";
115 private YamahaMusiccastStateDescriptionProvider stateDescriptionProvider;
117 public YamahaMusiccastHandler(Thing thing, YamahaMusiccastStateDescriptionProvider stateDescriptionProvider) {
119 this.stateDescriptionProvider = stateDescriptionProvider;
123 public void handleCommand(ChannelUID channelUID, Command command) {
124 String localValueToCheck = "";
125 String localRole = "";
126 boolean localSyncVolume;
127 String localDefaultAfterMCLink = "";
128 String localRoleSelectedThing = "";
129 if (command != RefreshType.REFRESH) {
130 logger.trace("Handling command {} for channel {}", command, channelUID);
131 channelWithoutGroup = channelUID.getIdWithoutGroup();
132 zone = channelUID.getGroupId();
133 DistributionInfo distributioninfo = new DistributionInfo();
134 Response response = new Response();
135 switch (channelWithoutGroup) {
137 if (command == OnOffType.ON) {
138 httpResponse = setPower("on", zone, this.host);
139 response = gson.fromJson(httpResponse, Response.class);
140 if (response != null) {
141 localValueToCheck = response.getResponseCode();
142 if (!"0".equals(localValueToCheck)) {
143 updateState(channelUID, OnOffType.OFF);
146 // check on scheduler task for UDP events
147 ScheduledFuture<?> localGeneralHousekeepingTask = generalHousekeepingTask;
148 if (localGeneralHousekeepingTask == null) {
149 logger.trace("YXC - No scheduler task found!");
150 generalHousekeepingTask = scheduler.scheduleWithFixedDelay(this::generalHousekeeping, 5,
151 300, TimeUnit.SECONDS);
154 logger.trace("Scheduler task found!");
157 } else if (command == OnOffType.OFF) {
158 httpResponse = setPower("standby", zone, this.host);
159 response = gson.fromJson(httpResponse, Response.class);
161 if (response != null) {
162 localValueToCheck = response.getResponseCode();
163 if (!"0".equals(localValueToCheck)) {
164 updateState(channelUID, OnOffType.ON);
170 if (command == OnOffType.ON) {
171 httpResponse = setMute("true", zone, this.host);
172 response = gson.fromJson(httpResponse, Response.class);
173 if (response != null) {
174 localValueToCheck = response.getResponseCode();
175 if (!"0".equals(localValueToCheck)) {
176 updateState(channelUID, OnOffType.OFF);
179 } else if (command == OnOffType.OFF) {
180 httpResponse = setMute("false", zone, this.host);
181 response = gson.fromJson(httpResponse, Response.class);
182 if (response != null) {
183 localValueToCheck = response.getResponseCode();
184 if (!"0".equals(localValueToCheck)) {
185 updateState(channelUID, OnOffType.ON);
191 volumePercent = Integer.parseInt(command.toString().replace(".0", ""));
192 volumeAbsValue = (maxVolumeState * volumePercent) / 100;
193 setVolume(volumeAbsValue, zone, this.host);
194 localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString());
195 if (localSyncVolume == Boolean.TRUE) {
196 tmpString = getDistributionInfo(this.host);
197 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
198 if (distributioninfo != null) {
199 localRole = distributioninfo.getRole();
200 if ("server".equals(localRole)) {
201 for (JsonElement ip : distributioninfo.getClientList()) {
202 JsonObject clientObject = ip.getAsJsonObject();
203 setVolumeLinkedDevice(volumePercent, zone,
204 clientObject.get("ip_address").getAsString());
208 } // END config.syncVolume
210 case CHANNEL_VOLUMEABS:
211 volumeAbsValue = Integer.parseInt(command.toString().replace(".0", ""));
212 volumePercent = (volumeAbsValue / maxVolumeState) * 100;
213 setVolume(volumeAbsValue, zone, this.host);
214 localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString());
215 if (localSyncVolume == Boolean.TRUE) {
216 tmpString = getDistributionInfo(this.host);
217 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
218 if (distributioninfo != null) {
219 localRole = distributioninfo.getRole();
220 if ("server".equals(localRole)) {
221 for (JsonElement ip : distributioninfo.getClientList()) {
222 JsonObject clientObject = ip.getAsJsonObject();
223 setVolumeLinkedDevice(volumePercent, zone,
224 clientObject.get("ip_address").getAsString());
230 case CHANNEL_VOLUMEDB:
231 setVolumeDb(((QuantityType<?>) command).floatValue(), zone, this.host);
232 localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString());
233 if (localSyncVolume == Boolean.TRUE) {
234 tmpString = getDistributionInfo(this.host);
235 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
236 if (distributioninfo != null) {
237 localRole = distributioninfo.getRole();
238 if ("server".equals(localRole)) {
239 for (JsonElement ip : distributioninfo.getClientList()) {
240 JsonObject clientObject = ip.getAsJsonObject();
241 setVolumeDbLinkedDevice(((DecimalType) command).floatValue(), zone,
242 clientObject.get("ip_address").getAsString());
249 // if it is a client, disconnect it first.
250 tmpString = getDistributionInfo(this.host);
251 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
252 if (distributioninfo != null) {
253 localRole = distributioninfo.getRole();
254 if ("client".equals(localRole)) {
255 json = "{\"group_id\":\"\"}";
256 httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
259 setInput(command.toString(), zone, this.host);
261 case CHANNEL_SOUNDPROGRAM:
262 setSoundProgram(command.toString(), zone, this.host);
264 case CHANNEL_SELECTPRESET:
265 setPreset(command.toString(), zone, this.host);
268 if (command.equals(PlayPauseType.PLAY)) {
269 setPlayback("play", this.host);
270 } else if (command.equals(PlayPauseType.PAUSE)) {
271 setPlayback("pause", this.host);
272 } else if (command.equals(NextPreviousType.NEXT)) {
273 setPlayback("next", this.host);
274 } else if (command.equals(NextPreviousType.PREVIOUS)) {
275 setPlayback("previous", this.host);
276 } else if (command.equals(RewindFastforwardType.REWIND)) {
277 setPlayback("fast_reverse_start", this.host);
278 } else if (command.equals(RewindFastforwardType.FASTFORWARD)) {
279 setPlayback("fast_forward_end", this.host);
283 setSleep(command.toString(), zone, this.host);
285 case CHANNEL_MCLINKSTATUS:
288 tmpString = getDistributionInfo(this.host);
289 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
290 if (distributioninfo != null) {
291 responseCode = distributioninfo.getResponseCode();
292 localRole = distributioninfo.getRole();
293 if (command.toString().equals("")) {
295 groupId = distributioninfo.getGroupId();
296 } else if (command.toString().contains("***")) {
298 String[] parts = command.toString().split("\\*\\*\\*");
299 if (parts.length > 1) {
300 mclinkSetupServer = parts[0];
301 mclinkSetupZone = parts[1];
302 tmpString = getDistributionInfo(mclinkSetupServer);
303 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
304 if (distributioninfo != null) {
305 responseCode = distributioninfo.getResponseCode();
306 localRoleSelectedThing = distributioninfo.getRole();
307 groupId = distributioninfo.getGroupId();
308 if (localRoleSelectedThing != null) {
309 if ("server".equals(localRoleSelectedThing)) {
310 groupId = distributioninfo.getGroupId();
311 } else if ("client".equals(localRoleSelectedThing)) {
313 } else if ("none".equals(localRoleSelectedThing)) {
314 groupId = generateGroupId();
321 if ("unlink".equals(action)) {
322 json = "{\"group_id\":\"\"}";
323 if (localRole != null) {
324 if ("server".equals(localRole)) {
325 httpResponse = setClientServerInfo(this.host, json, "setServerInfo");
326 // Set GroupId = "" for linked clients
327 if (distributioninfo != null) {
328 for (JsonElement ip : distributioninfo.getClientList()) {
329 JsonObject clientObject = ip.getAsJsonObject();
330 setClientServerInfo(clientObject.get("ip_address").getAsString(), json,
334 } else if ("client".equals(localRole)) {
335 mclinkSetupServer = connectedServer();
336 // Step 1. empty group on client
337 httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
338 // empty zone to respect defaults
339 if (!"".equals(mclinkSetupServer)) {
340 // Step 2. remove client from server
341 json = "{\"group_id\":\"" + groupId
342 + "\", \"type\":\"remove\", \"client_list\":[\"" + this.host + "\"]}";
343 httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo");
344 // Step 3. reflect changes to master
345 httpResponse = startDistribution(mclinkSetupServer);
346 localDefaultAfterMCLink = getThing().getConfiguration()
347 .get("defaultAfterMCLink").toString();
348 httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host);
349 } else if ("".equals(mclinkSetupServer)) {
350 // fallback in case client is removed from group by ending group on server side
351 localDefaultAfterMCLink = getThing().getConfiguration()
352 .get("defaultAfterMCLink").toString();
353 httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host);
357 } else if ("link".equals(action)) {
358 if (localRole != null) {
359 if ("none".equals(localRole)) {
360 json = "{\"group_id\":\"" + groupId + "\", \"zone\":\"" + mclinkSetupZone
361 + "\", \"type\":\"add\", \"client_list\":[\"" + this.host + "\"]}";
362 logger.trace("setServerInfo json: {}", json);
363 httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo");
364 // All zones of Model are required for MC Link
366 for (int i = 1; i <= zoneNum; i++) {
369 tmpString = "\"main\"";
372 tmpString = tmpString + ", \"zone2\"";
375 tmpString = tmpString + ", \"zone3\"";
378 tmpString = tmpString + ", \"zone4\"";
382 json = "{\"group_id\":\"" + groupId + "\", \"zone\":[" + tmpString + "]}";
383 logger.trace("setClientInfo json: {}", json);
384 httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
385 httpResponse = startDistribution(mclinkSetupServer);
390 updateMCLinkStatus();
392 case CHANNEL_RECALLSCENE:
393 recallScene(command.toString(), zone, this.host);
396 setRepeat(command.toString(), this.host);
398 case CHANNEL_SHUFFLE:
399 setShuffle(command.toString(), this.host);
401 } // END Switch Channel
406 public void initialize() {
407 String localHost = "";
408 thingLabel = thing.getLabel();
409 updateStatus(ThingStatus.UNKNOWN);
410 localHost = getThing().getConfiguration().get("host").toString();
411 this.host = localHost;
412 if (!"".equals(this.host)) {
413 zoneNum = getNumberOfZones(this.host);
414 logger.trace("Zones found: {} - {}", zoneNum, thingLabel);
418 generalHousekeepingTask = scheduler.scheduleWithFixedDelay(this::generalHousekeeping, 5, 300,
420 updateStatus(ThingStatus.ONLINE);
422 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "No host found");
427 private void generalHousekeeping() {
428 thingLabel = thing.getLabel();
429 logger.trace("YXC - Start Keep Alive UDP events (5 minutes - {}) ", thingLabel);
430 keepUdpEventsAlive(this.host);
431 fillOptionsForMCLink();
432 updateMCLinkStatus();
435 private void refreshOnStartup() {
436 for (int i = 1; i <= zoneNum; i++) {
439 createChannels("main");
440 updateStatusZone("main");
443 createChannels("zone2");
444 updateStatusZone("zone2");
447 createChannels("zone3");
448 updateStatusZone("zone3");
451 createChannels("zone4");
452 updateStatusZone("zone4");
457 updateNetUSBPlayer();
458 fillOptionsForMCLink();
459 updateMCLinkStatus();
463 public void dispose() {
464 ScheduledFuture<?> localGeneralHousekeepingTask = generalHousekeepingTask;
465 if (localGeneralHousekeepingTask != null) {
466 localGeneralHousekeepingTask.cancel(true);
472 private void createChannels(String zone) {
473 createChannel(zone, CHANNEL_POWER, CHANNEL_TYPE_UID_POWER, "Switch");
474 createChannel(zone, CHANNEL_MUTE, CHANNEL_TYPE_UID_MUTE, "Switch");
475 createChannel(zone, CHANNEL_VOLUME, CHANNEL_TYPE_UID_VOLUME, "Dimmer");
476 createChannel(zone, CHANNEL_VOLUMEABS, CHANNEL_TYPE_UID_VOLUMEABS, "Number");
477 createChannel(zone, CHANNEL_VOLUMEDB, CHANNEL_TYPE_UID_VOLUMEDB, "Number:Dimensionless");
478 createChannel(zone, CHANNEL_INPUT, CHANNEL_TYPE_UID_INPUT, "String");
479 createChannel(zone, CHANNEL_SOUNDPROGRAM, CHANNEL_TYPE_UID_SOUNDPROGRAM, "String");
480 createChannel(zone, CHANNEL_SLEEP, CHANNEL_TYPE_UID_SLEEP, "Number");
481 createChannel(zone, CHANNEL_SELECTPRESET, CHANNEL_TYPE_UID_SELECTPRESET, "String");
482 createChannel(zone, CHANNEL_RECALLSCENE, CHANNEL_TYPE_UID_RECALLSCENE, "Number");
483 createChannel(zone, CHANNEL_MCLINKSTATUS, CHANNEL_TYPE_UID_MCLINKSTATUS, "String");
486 private void createChannel(String zone, String channel, ChannelTypeUID channelTypeUID, String itemType) {
487 ChannelUID channelToCheck = new ChannelUID(thing.getUID(), zone, channel);
488 if (thing.getChannel(channelToCheck) == null) {
489 ThingBuilder thingBuilder = editThing();
490 Channel testchannel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), zone, channel), itemType)
491 .withType(channelTypeUID).build();
492 thingBuilder.withChannel(testchannel);
493 updateThing(thingBuilder.build());
497 private void powerOffCleanup() {
499 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST);
500 updateState(channel, StringType.valueOf("-"));
501 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK);
502 updateState(channel, StringType.valueOf("-"));
503 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM);
504 updateState(channel, StringType.valueOf("-"));
507 public void processUDPEvent(String json, String trackingID) {
508 logger.trace("UDP package: {} (Tracking: {})", json, trackingID);
510 UdpMessage targetObject = gson.fromJson(json, UdpMessage.class);
511 if (targetObject != null) {
512 if (Objects.nonNull(targetObject.getMain())) {
513 updateStateFromUDPEvent("main", targetObject);
515 if (Objects.nonNull(targetObject.getZone2())) {
516 updateStateFromUDPEvent("zone2", targetObject);
518 if (Objects.nonNull(targetObject.getZone3())) {
519 updateStateFromUDPEvent("zone3", targetObject);
521 if (Objects.nonNull(targetObject.getZone4())) {
522 updateStateFromUDPEvent("zone4", targetObject);
524 if (Objects.nonNull(targetObject.getNetUSB())) {
525 updateStateFromUDPEvent("netusb", targetObject);
527 if (Objects.nonNull(targetObject.getDist())) {
528 updateStateFromUDPEvent("dist", targetObject);
533 private void updateStateFromUDPEvent(String zoneToUpdate, UdpMessage targetObject) {
535 String playInfoUpdated = "";
536 String statusUpdated = "";
537 String powerState = "";
538 String muteState = "";
539 String inputState = "";
541 ActualVolume actualVolume = null;
542 int presetNumber = 0;
544 String distInfoUpdated = "";
545 logger.trace("Handling UDP for {}", zoneToUpdate);
546 switch (zoneToUpdate) {
548 powerState = targetObject.getMain().getPower();
549 muteState = targetObject.getMain().getMute();
550 inputState = targetObject.getMain().getInput();
551 volumeState = targetObject.getMain().getVolume();
552 actualVolume = targetObject.getMain().getActualVolume();
553 statusUpdated = targetObject.getMain().getstatusUpdated();
556 powerState = targetObject.getZone2().getPower();
557 muteState = targetObject.getZone2().getMute();
558 inputState = targetObject.getZone2().getInput();
559 volumeState = targetObject.getZone2().getVolume();
560 actualVolume = targetObject.getZone2().getActualVolume();
561 statusUpdated = targetObject.getZone2().getstatusUpdated();
564 powerState = targetObject.getZone3().getPower();
565 muteState = targetObject.getZone3().getMute();
566 inputState = targetObject.getZone3().getInput();
567 volumeState = targetObject.getZone3().getVolume();
568 actualVolume = targetObject.getZone3().getActualVolume();
569 statusUpdated = targetObject.getZone3().getstatusUpdated();
572 powerState = targetObject.getZone4().getPower();
573 muteState = targetObject.getZone4().getMute();
574 inputState = targetObject.getZone4().getInput();
575 volumeState = targetObject.getZone4().getVolume();
576 actualVolume = targetObject.getZone4().getActualVolume();
577 statusUpdated = targetObject.getZone4().getstatusUpdated();
580 if (Objects.isNull(targetObject.getNetUSB().getPresetControl())) {
583 presetNumber = targetObject.getNetUSB().getPresetControl().getNum();
585 playInfoUpdated = targetObject.getNetUSB().getPlayInfoUpdated();
586 playTime = targetObject.getNetUSB().getPlayTime();
587 // totalTime is not in UDP event
590 distInfoUpdated = targetObject.getDist().getDistInfoUpdated();
594 if (logger.isTraceEnabled()) {
595 logger.trace("{} - Response: {}", zoneToUpdate, responseCode);
596 logger.trace("{} - Power: {}", zoneToUpdate, powerState);
597 logger.trace("{} - Mute: {}", zoneToUpdate, muteState);
598 logger.trace("{} - Volume: {}", zoneToUpdate, volumeState);
599 logger.trace("{} - Volume in dB: {}", zoneToUpdate, (actualVolume != null) ? actualVolume.getValue() : "");
600 logger.trace("{} - Max Volume: {}", zoneToUpdate, maxVolumeState);
601 logger.trace("{} - Input: {}", zoneToUpdate, inputState);
602 logger.trace("{} - Soundprogram: {}", zoneToUpdate, soundProgramState);
603 logger.trace("{} - Sleep: {}", zoneToUpdate, sleepState);
606 if (!powerState.isEmpty()) {
607 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_POWER);
608 if ("on".equals(powerState)) {
609 updateState(channel, OnOffType.ON);
610 } else if ("standby".equals(powerState)) {
611 updateState(channel, OnOffType.OFF);
616 if (!muteState.isEmpty()) {
617 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_MUTE);
618 if ("true".equals(muteState)) {
619 updateState(channel, OnOffType.ON);
620 } else if ("false".equals(muteState)) {
621 updateState(channel, OnOffType.OFF);
625 if (!inputState.isEmpty()) {
626 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_INPUT);
627 updateState(channel, StringType.valueOf(inputState));
630 if (volumeState != 0) {
631 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUME);
632 updateState(channel, new PercentType((volumeState * 100) / maxVolumeState));
633 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUMEABS);
634 updateState(channel, new DecimalType(volumeState));
637 if (actualVolume != null) {
638 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUMEDB);
639 updateState(channel, new QuantityType<>(actualVolume.getValue(), Units.DECIBEL));
642 if (presetNumber != 0) {
643 logger.trace("Preset detected: {}", presetNumber);
644 updatePresets(presetNumber);
647 if ("true".equals(playInfoUpdated)) {
648 updateNetUSBPlayer();
651 if (!statusUpdated.isEmpty()) {
652 updateStatusZone(zoneToUpdate);
655 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME);
656 updateState(channel, StringType.valueOf(String.valueOf(playTime)));
658 if ("true".equals(distInfoUpdated)) {
659 updateMCLinkStatus();
663 private void updateStatusZone(String zoneToUpdate) {
664 String localZone = "";
665 tmpString = getStatus(this.host, zoneToUpdate);
667 Status targetObject = gson.fromJson(tmpString, Status.class);
668 if (targetObject != null) {
669 String responseCode = targetObject.getResponseCode();
670 String powerState = targetObject.getPower();
671 String muteState = targetObject.getMute();
672 volumeState = targetObject.getVolume();
673 ActualVolume actualVolume = targetObject.getActualVolume();
674 maxVolumeState = targetObject.getMaxVolume();
675 inputState = targetObject.getInput();
676 soundProgramState = targetObject.getSoundProgram();
677 sleepState = targetObject.getSleep();
679 if (logger.isTraceEnabled()) {
680 logger.trace("{} - Response: {}", zoneToUpdate, responseCode);
681 logger.trace("{} - Power: {}", zoneToUpdate, powerState);
682 logger.trace("{} - Mute: {}", zoneToUpdate, muteState);
683 logger.trace("{} - Volume: {}", zoneToUpdate, volumeState);
684 logger.trace("{} - Volume in dB: {}", zoneToUpdate,
685 (actualVolume != null) ? actualVolume.getValue() : "");
686 logger.trace("{} - Max Volume: {}", zoneToUpdate, maxVolumeState);
687 logger.trace("{} - Input: {}", zoneToUpdate, inputState);
688 logger.trace("{} - Soundprogram: {}", zoneToUpdate, soundProgramState);
689 logger.trace("{} - Sleep: {}", zoneToUpdate, sleepState);
692 switch (responseCode) {
694 for (Channel channel : getThing().getChannels()) {
695 ChannelUID channelUID = channel.getUID();
696 channelWithoutGroup = channelUID.getIdWithoutGroup();
697 localZone = channelUID.getGroupId();
698 if (localZone != null) {
699 if (isLinked(channelUID)) {
700 switch (channelWithoutGroup) {
702 if ("on".equals(powerState)) {
703 if (localZone.equals(zoneToUpdate)) {
704 updateState(channelUID, OnOffType.ON);
706 } else if ("standby".equals(powerState)) {
707 if (localZone.equals(zoneToUpdate)) {
708 updateState(channelUID, OnOffType.OFF);
713 if ("true".equals(muteState)) {
714 if (localZone.equals(zoneToUpdate)) {
715 updateState(channelUID, OnOffType.ON);
717 } else if ("false".equals(muteState)) {
718 if (localZone.equals(zoneToUpdate)) {
719 updateState(channelUID, OnOffType.OFF);
724 if (localZone.equals(zoneToUpdate)) {
725 updateState(channelUID,
726 new PercentType((volumeState * 100) / maxVolumeState));
729 case CHANNEL_VOLUMEABS:
730 if (localZone.equals(zoneToUpdate)) {
731 updateState(channelUID, new DecimalType(volumeState));
734 case CHANNEL_VOLUMEDB:
735 if (localZone.equals(zoneToUpdate)) {
736 if (actualVolume != null) {
737 updateState(channelUID,
738 new QuantityType<>(actualVolume.getValue(), Units.DECIBEL));
740 updateState(channelUID, UnDefType.UNDEF);
745 if (localZone.equals(zoneToUpdate)) {
746 updateState(channelUID, StringType.valueOf(inputState));
749 case CHANNEL_SOUNDPROGRAM:
750 if (localZone.equals(zoneToUpdate)) {
751 updateState(channelUID, StringType.valueOf(soundProgramState));
755 if (localZone.equals(zoneToUpdate)) {
756 updateState(channelUID, new DecimalType(sleepState));
759 } // END switch (channelWithoutGroup)
765 logger.trace("Nothing to do! - {} ({})", thingLabel, zoneToUpdate);
771 private void updatePresets(int value) {
772 String inputText = "";
773 int presetCounter = 0;
774 int currentPreset = 0;
775 tmpString = getPresetInfo(this.host);
777 PresetInfo presetinfo = gson.fromJson(tmpString, PresetInfo.class);
778 if (presetinfo != null) {
779 String responseCode = presetinfo.getResponseCode();
780 if ("0".equals(responseCode)) {
781 List<StateOption> optionsPresets = new ArrayList<>();
782 inputText = getLastInput();
783 if (inputText != null) {
784 for (JsonElement pr : presetinfo.getPresetInfo()) {
785 presetCounter = presetCounter + 1;
786 JsonObject presetObject = pr.getAsJsonObject();
787 String text = presetObject.get("text").getAsString();
788 if (!"".equals(text)) {
789 optionsPresets.add(new StateOption(String.valueOf(presetCounter),
790 "#" + String.valueOf(presetCounter) + " " + text));
791 if (inputText.equals(text)) {
792 currentPreset = presetCounter;
798 currentPreset = value;
800 for (Channel channel : getThing().getChannels()) {
801 ChannelUID channelUID = channel.getUID();
802 channelWithoutGroup = channelUID.getIdWithoutGroup();
803 if (isLinked(channelUID)) {
804 switch (channelWithoutGroup) {
805 case CHANNEL_SELECTPRESET:
806 stateDescriptionProvider.setStateOptions(channelUID, optionsPresets);
807 updateState(channelUID, StringType.valueOf(String.valueOf(currentPreset)));
816 private void updateNetUSBPlayer() {
817 tmpString = getPlayInfo(this.host);
820 PlayInfo targetObject = gson.fromJson(tmpString, PlayInfo.class);
821 if (targetObject != null) {
822 String responseCode = targetObject.getResponseCode();
823 String playbackState = targetObject.getPlayback();
824 artistState = targetObject.getArtist();
825 trackState = targetObject.getTrack();
826 albumState = targetObject.getAlbum();
827 String albumArtUrlState = targetObject.getAlbumArtUrl();
828 repeatState = targetObject.getRepeat();
829 shuffleState = targetObject.getShuffle();
830 playTimeState = targetObject.getPlayTime();
831 totalTimeState = targetObject.getTotalTime();
833 if ("0".equals(responseCode)) {
834 ChannelUID testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYER);
835 switch (playbackState) {
837 updateState(testchannel, PlayPauseType.PLAY);
840 updateState(testchannel, PlayPauseType.PAUSE);
843 updateState(testchannel, PlayPauseType.PAUSE);
846 updateState(testchannel, RewindFastforwardType.REWIND);
849 updateState(testchannel, RewindFastforwardType.FASTFORWARD);
852 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST);
853 updateState(testchannel, StringType.valueOf(artistState));
854 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK);
855 updateState(testchannel, StringType.valueOf(trackState));
856 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM);
857 updateState(testchannel, StringType.valueOf(albumState));
858 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUMART);
859 if (!"".equals(albumArtUrlState)) {
860 albumArtUrlState = HTTP + this.host + albumArtUrlState;
862 updateState(testchannel, StringType.valueOf(albumArtUrlState));
863 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_REPEAT);
864 updateState(testchannel, StringType.valueOf(repeatState));
865 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_SHUFFLE);
866 updateState(testchannel, StringType.valueOf(shuffleState));
867 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME);
868 updateState(testchannel, StringType.valueOf(String.valueOf(playTimeState)));
869 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TOTALTIME);
870 updateState(testchannel, StringType.valueOf(String.valueOf(totalTimeState)));
875 private @Nullable String getLastInput() {
877 tmpString = getRecentInfo(this.host);
878 RecentInfo recentinfo = gson.fromJson(tmpString, RecentInfo.class);
879 if (recentinfo != null) {
880 String responseCode = recentinfo.getResponseCode();
881 if ("0".equals(responseCode)) {
882 for (JsonElement ri : recentinfo.getRecentInfo()) {
883 JsonObject recentObject = ri.getAsJsonObject();
884 text = recentObject.get("text").getAsString();
892 private String connectedServer() {
893 DistributionInfo distributioninfo = new DistributionInfo();
894 Bridge bridge = getBridge();
895 String remotehost = "";
897 String localHost = "";
898 if (bridge != null) {
899 for (Thing thing : bridge.getThings()) {
900 remotehost = thing.getConfiguration().get("host").toString();
901 tmpString = getDistributionInfo(remotehost);
902 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
903 if (distributioninfo != null) {
904 String localRole = distributioninfo.getRole();
905 if ("server".equals(localRole)) {
906 for (JsonElement ip : distributioninfo.getClientList()) {
907 JsonObject clientObject = ip.getAsJsonObject();
908 localHost = getThing().getConfiguration().get("host").toString();
909 if (localHost.equals(clientObject.get("ip_address").getAsString())) {
921 private void fillOptionsForMCLink() {
922 Bridge bridge = getBridge();
925 int zonesPerHost = 1;
927 tmpString = getDistributionInfo(this.host);
928 DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class);
929 if (targetObject != null) {
930 clients = targetObject.getClientList().size();
933 List<StateOption> options = new ArrayList<>();
934 // first add 3 options for MC Link
935 options.add(new StateOption("", "Standalone"));
936 options.add(new StateOption("server", "Server: " + clients + " clients"));
937 options.add(new StateOption("client", "Client"));
939 if (bridge != null) {
940 for (Thing thing : bridge.getThings()) {
941 label = thing.getLabel();
942 host = thing.getConfiguration().get("host").toString();
943 logger.trace("Thing found on Bridge: {} - {}", label, host);
944 zonesPerHost = getNumberOfZones(host);
945 for (int i = 1; i <= zonesPerHost; i++) {
948 options.add(new StateOption(host + "***main", label + " - main (" + host + ")"));
951 options.add(new StateOption(host + "***zone2", label + " - zone2 (" + host + ")"));
954 options.add(new StateOption(host + "***zone3", label + " - zone3 (" + host + ")"));
957 options.add(new StateOption(host + "***zone4", label + " - zone4 (" + host + ")"));
964 // for each zone of the device, set all the possible combinations
965 ChannelUID testchannel;
966 for (int i = 1; i <= zoneNum; i++) {
969 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
970 if (isLinked(testchannel)) {
971 stateDescriptionProvider.setStateOptions(testchannel, options);
975 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
976 if (isLinked(testchannel)) {
977 stateDescriptionProvider.setStateOptions(testchannel, options);
981 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
982 if (isLinked(testchannel)) {
983 stateDescriptionProvider.setStateOptions(testchannel, options);
987 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
988 if (isLinked(testchannel)) {
989 stateDescriptionProvider.setStateOptions(testchannel, options);
996 private String generateGroupId() {
997 return UUID.randomUUID().toString().replace("-", "").substring(0, 32);
1000 private int getNumberOfZones(@Nullable String host) {
1001 int numberOfZones = 0;
1002 tmpString = getFeatures(host);
1004 Features targetObject = gson.fromJson(tmpString, Features.class);
1005 if (targetObject != null) {
1006 responseCode = targetObject.getResponseCode();
1007 if ("0".equals(responseCode)) {
1008 numberOfZones = targetObject.getSystem().getZoneNum();
1011 return numberOfZones;
1014 public @Nullable String getDeviceId() {
1015 tmpString = getDeviceInfo(this.host);
1016 String localValueToCheck = "";
1018 DeviceInfo targetObject = gson.fromJson(tmpString, DeviceInfo.class);
1019 if (targetObject != null) {
1020 localValueToCheck = targetObject.getDeviceId();
1022 return localValueToCheck;
1025 private void setVolumeLinkedDevice(int value, @Nullable String zone, String host) {
1026 logger.trace("setVolumeLinkedDevice: {}", host);
1027 int zoneNumLinkedDevice = getNumberOfZones(host);
1028 int maxVolumeLinkedDevice = 0;
1030 Status targetObject = new Status();
1032 for (int i = 1; i <= zoneNumLinkedDevice; i++) {
1035 tmpString = getStatus(host, "main");
1036 targetObject = gson.fromJson(tmpString, Status.class);
1037 if (targetObject != null) {
1038 responseCode = targetObject.getResponseCode();
1039 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1040 newVolume = maxVolumeLinkedDevice * value / 100;
1041 setVolume(newVolume, "main", host);
1045 tmpString = getStatus(host, "zone2");
1046 targetObject = gson.fromJson(tmpString, Status.class);
1047 if (targetObject != null) {
1048 responseCode = targetObject.getResponseCode();
1049 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1050 newVolume = maxVolumeLinkedDevice * value / 100;
1051 setVolume(newVolume, "zone2", host);
1055 tmpString = getStatus(host, "zone3");
1056 targetObject = gson.fromJson(tmpString, Status.class);
1057 if (targetObject != null) {
1058 responseCode = targetObject.getResponseCode();
1059 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1060 newVolume = maxVolumeLinkedDevice * value / 100;
1061 setVolume(newVolume, "zone3", host);
1065 tmpString = getStatus(host, "zone4");
1066 targetObject = gson.fromJson(tmpString, Status.class);
1067 if (targetObject != null) {
1068 responseCode = targetObject.getResponseCode();
1069 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1070 newVolume = maxVolumeLinkedDevice * value / 100;
1071 setVolume(newVolume, "zone4", host);
1078 private void setVolumeDbLinkedDevice(float value, @Nullable String zone, String host) {
1079 logger.trace("setVolumeDbLinkedDevice: {}", host);
1080 int zoneNumLinkedDevice = getNumberOfZones(host);
1081 for (int i = 1; i <= zoneNumLinkedDevice; i++) {
1084 setVolumeDb(value, "main", host);
1087 setVolumeDb(value, "zone2", host);
1090 setVolumeDb(value, "zone3", host);
1093 setVolumeDb(value, "zone4", host);
1099 public void updateMCLinkStatus() {
1100 tmpString = getDistributionInfo(this.host);
1102 DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class);
1103 if (targetObject != null) {
1104 String localRole = targetObject.getRole();
1105 groupId = targetObject.getGroupId();
1106 switch (localRole) {
1108 setMCLinkToStandalone();
1111 setMCLinkToServer();
1114 setMCLinkToClient();
1120 private void setMCLinkToStandalone() {
1121 ChannelUID testchannel;
1122 for (int i = 1; i <= zoneNum; i++) {
1125 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1126 updateState(testchannel, StringType.valueOf(""));
1129 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1130 updateState(testchannel, StringType.valueOf(""));
1133 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1134 updateState(testchannel, StringType.valueOf(""));
1137 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1138 updateState(testchannel, StringType.valueOf(""));
1144 private void setMCLinkToClient() {
1145 ChannelUID testchannel;
1146 for (int i = 1; i <= zoneNum; i++) {
1149 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1150 updateState(testchannel, StringType.valueOf("client"));
1153 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1154 updateState(testchannel, StringType.valueOf("client"));
1157 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1158 updateState(testchannel, StringType.valueOf("client"));
1161 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1162 updateState(testchannel, StringType.valueOf("client"));
1168 private void setMCLinkToServer() {
1169 ChannelUID testchannel;
1170 for (int i = 1; i <= zoneNum; i++) {
1173 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1174 updateState(testchannel, StringType.valueOf("server"));
1177 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1178 updateState(testchannel, StringType.valueOf("server"));
1181 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1182 updateState(testchannel, StringType.valueOf("server"));
1185 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1186 updateState(testchannel, StringType.valueOf("server"));
1192 private String makeRequest(@Nullable String topicAVR, String url) {
1193 String response = "";
1195 response = HttpUtil.executeUrl("GET", HTTP + url, LONG_CONNECTION_TIMEOUT_MILLISEC);
1196 logger.trace("{} - {}", topicAVR, response);
1198 } catch (IOException e) {
1199 logger.trace("IO Exception - {} - {}", topicAVR, e.getMessage());
1200 return "{\"response_code\":\"999\"}";
1203 // End Various functions
1207 // Start Zone Related
1209 private @Nullable String getStatus(@Nullable String host, String zone) {
1210 return makeRequest("Status", host + YAMAHA_EXTENDED_CONTROL + zone + "/getStatus");
1213 private @Nullable String setPower(String value, @Nullable String zone, @Nullable String host) {
1214 return makeRequest("Power", host + YAMAHA_EXTENDED_CONTROL + zone + "/setPower?power=" + value);
1217 private @Nullable String setMute(String value, @Nullable String zone, @Nullable String host) {
1218 return makeRequest("Mute", host + YAMAHA_EXTENDED_CONTROL + zone + "/setMute?enable=" + value);
1221 private @Nullable String setVolume(int value, @Nullable String zone, @Nullable String host) {
1222 return makeRequest("Volume", host + YAMAHA_EXTENDED_CONTROL + zone + "/setVolume?volume=" + value);
1226 * Sets the volume in decibels (dB).
1228 * @param value volume in dB (decibels)
1229 * @param zone name of zone
1230 * @param host hostname or ip address
1231 * @return HTTP request
1233 private @Nullable String setVolumeDb(float value, @Nullable String zone, @Nullable String host) {
1234 float volumeDbMin = Float.parseFloat(getThing().getConfiguration().get("volumeDbMin").toString());
1235 float volumeDbMax = Float.parseFloat(getThing().getConfiguration().get("volumeDbMax").toString());
1236 if (value < volumeDbMin) {
1237 value = volumeDbMin;
1239 if (value > volumeDbMax) {
1240 value = volumeDbMax;
1243 // Yamaha accepts only integer values with .0 or .5 at the end only (-20.5dB, -20.0dB) - at least on RX-S601D.
1244 // The order matters here. We want to cast to integer first and then scale by 10.
1245 // Effectively we're only allowing dB values with .0 at the end.
1246 logger.trace("setVolumeDb: {} dB", value);
1247 return makeRequest("Volume", host + YAMAHA_EXTENDED_CONTROL + zone + "/setActualVolume?mode=db&value=" + value);
1250 private @Nullable String setInput(String value, @Nullable String zone, @Nullable String host) {
1251 return makeRequest("setInput", host + YAMAHA_EXTENDED_CONTROL + zone + "/setInput?input=" + value);
1254 private @Nullable String setSoundProgram(String value, @Nullable String zone, @Nullable String host) {
1255 return makeRequest("setSoundProgram",
1256 host + YAMAHA_EXTENDED_CONTROL + zone + "/setSoundProgram?program=" + value);
1259 private @Nullable String setPreset(String value, @Nullable String zone, @Nullable String host) {
1260 return makeRequest("setPreset",
1261 host + YAMAHA_EXTENDED_CONTROL + "netusb/recallPreset?zone=" + zone + "&num=" + value);
1264 private @Nullable String setSleep(String value, @Nullable String zone, @Nullable String host) {
1265 return makeRequest("setSleep", host + YAMAHA_EXTENDED_CONTROL + zone + "/setSleep?sleep=" + value);
1268 private @Nullable String recallScene(String value, @Nullable String zone, @Nullable String host) {
1269 return makeRequest("recallScene", host + YAMAHA_EXTENDED_CONTROL + zone + "/recallScene?num=" + value);
1273 // Start Net Radio/USB Related
1275 private @Nullable String getPresetInfo(@Nullable String host) {
1276 return makeRequest("PresetInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPresetInfo");
1279 private @Nullable String getRecentInfo(@Nullable String host) {
1280 return makeRequest("RecentInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getRecentInfo");
1283 private @Nullable String getPlayInfo(@Nullable String host) {
1284 return makeRequest("PlayInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo");
1287 private @Nullable String setPlayback(String value, @Nullable String host) {
1288 return makeRequest("Playback", host + YAMAHA_EXTENDED_CONTROL + "netusb/setPlayback?playback=" + value);
1291 private @Nullable String setRepeat(String value, @Nullable String host) {
1292 return makeRequest("Repeat", host + YAMAHA_EXTENDED_CONTROL + "netusb/setRepeat?mode=" + value);
1295 private @Nullable String setShuffle(String value, @Nullable String host) {
1296 return makeRequest("Shuffle", host + YAMAHA_EXTENDED_CONTROL + "netusb/setShuffle?mode=" + value);
1299 // End Net Radio/USB Related
1301 // Start Music Cast API calls
1302 private @Nullable String getDistributionInfo(@Nullable String host) {
1303 return makeRequest("DistributionInfo", host + YAMAHA_EXTENDED_CONTROL + "dist/getDistributionInfo");
1306 private @Nullable String setClientServerInfo(@Nullable String host, String json, String type) {
1307 InputStream is = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
1309 url = "http://" + host + YAMAHA_EXTENDED_CONTROL + "dist/" + type;
1310 httpResponse = HttpUtil.executeUrl("POST", url, is, "", LONG_CONNECTION_TIMEOUT_MILLISEC);
1311 logger.trace("MC Link/Unlink Client {}", httpResponse);
1312 return httpResponse;
1313 } catch (IOException e) {
1314 logger.trace("IO Exception - {} - {}", type, e.getMessage());
1315 return "{\"response_code\":\"999\"}";
1319 private @Nullable String startDistribution(@Nullable String host) {
1320 Random ran = new Random();
1321 int nxt = ran.nextInt(200000);
1322 return makeRequest("StartDistribution", host + YAMAHA_EXTENDED_CONTROL + "dist/startDistribution?num=" + nxt);
1325 // End Music Cast API calls
1327 // Start General/System API calls
1329 private @Nullable String getFeatures(@Nullable String host) {
1330 return makeRequest("Features", host + YAMAHA_EXTENDED_CONTROL + "system/getFeatures");
1333 private @Nullable String getDeviceInfo(@Nullable String host) {
1334 return makeRequest("DeviceInfo", host + YAMAHA_EXTENDED_CONTROL + "system/getDeviceInfo");
1337 private void keepUdpEventsAlive(@Nullable String host) {
1338 Properties appProps = new Properties();
1339 appProps.setProperty("X-AppName", "MusicCast/1");
1340 appProps.setProperty("X-AppPort", "41100");
1342 httpResponse = HttpUtil.executeUrl("GET", HTTP + host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo",
1343 appProps, null, "", LONG_CONNECTION_TIMEOUT_MILLISEC);
1344 // logger.trace("{}", httpResponse);
1345 logger.trace("{} - {}", "UDP task", httpResponse);
1346 } catch (IOException e) {
1347 logger.trace("UDP refresh failed - {}", e.getMessage());
1350 // End General/System API calls