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:
232 if (command instanceof QuantityType<?> qt) {
233 volumeDb = qt.toUnit(Units.DECIBEL).floatValue();
234 } else if (command instanceof DecimalType dt) {
235 volumeDb = dt.floatValue();
237 logger.debug("Command has wrong type, QuantityType or DecimalType required!");
240 setVolumeDb(volumeDb, zone, this.host);
241 localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString());
242 if (localSyncVolume == Boolean.TRUE) {
243 tmpString = getDistributionInfo(this.host);
244 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
245 if (distributioninfo != null) {
246 localRole = distributioninfo.getRole();
247 if ("server".equals(localRole)) {
248 for (JsonElement ip : distributioninfo.getClientList()) {
249 JsonObject clientObject = ip.getAsJsonObject();
250 setVolumeDbLinkedDevice(volumeDb, zone,
251 clientObject.get("ip_address").getAsString());
258 // if it is a client, disconnect it first.
259 tmpString = getDistributionInfo(this.host);
260 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
261 if (distributioninfo != null) {
262 localRole = distributioninfo.getRole();
263 if ("client".equals(localRole)) {
264 json = "{\"group_id\":\"\"}";
265 httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
268 setInput(command.toString(), zone, this.host);
270 case CHANNEL_SOUNDPROGRAM:
271 setSoundProgram(command.toString(), zone, this.host);
273 case CHANNEL_SELECTPRESET:
274 setPreset(command.toString(), zone, this.host);
277 if (command.equals(PlayPauseType.PLAY)) {
278 setPlayback("play", this.host);
279 } else if (command.equals(PlayPauseType.PAUSE)) {
280 setPlayback("pause", this.host);
281 } else if (command.equals(NextPreviousType.NEXT)) {
282 setPlayback("next", this.host);
283 } else if (command.equals(NextPreviousType.PREVIOUS)) {
284 setPlayback("previous", this.host);
285 } else if (command.equals(RewindFastforwardType.REWIND)) {
286 setPlayback("fast_reverse_start", this.host);
287 } else if (command.equals(RewindFastforwardType.FASTFORWARD)) {
288 setPlayback("fast_forward_end", this.host);
292 setSleep(command.toString(), zone, this.host);
294 case CHANNEL_MCLINKSTATUS:
297 tmpString = getDistributionInfo(this.host);
298 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
299 if (distributioninfo != null) {
300 responseCode = distributioninfo.getResponseCode();
301 localRole = distributioninfo.getRole();
302 if (command.toString().equals("")) {
304 groupId = distributioninfo.getGroupId();
305 } else if (command.toString().contains("***")) {
307 String[] parts = command.toString().split("\\*\\*\\*");
308 if (parts.length > 1) {
309 mclinkSetupServer = parts[0];
310 mclinkSetupZone = parts[1];
311 tmpString = getDistributionInfo(mclinkSetupServer);
312 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
313 if (distributioninfo != null) {
314 responseCode = distributioninfo.getResponseCode();
315 localRoleSelectedThing = distributioninfo.getRole();
316 groupId = distributioninfo.getGroupId();
317 if (localRoleSelectedThing != null) {
318 if ("server".equals(localRoleSelectedThing)) {
319 groupId = distributioninfo.getGroupId();
320 } else if ("client".equals(localRoleSelectedThing)) {
322 } else if ("none".equals(localRoleSelectedThing)) {
323 groupId = generateGroupId();
330 if ("unlink".equals(action)) {
331 json = "{\"group_id\":\"\"}";
332 if (localRole != null) {
333 if ("server".equals(localRole)) {
334 httpResponse = setClientServerInfo(this.host, json, "setServerInfo");
335 // Set GroupId = "" for linked clients
336 if (distributioninfo != null) {
337 for (JsonElement ip : distributioninfo.getClientList()) {
338 JsonObject clientObject = ip.getAsJsonObject();
339 setClientServerInfo(clientObject.get("ip_address").getAsString(), json,
343 } else if ("client".equals(localRole)) {
344 mclinkSetupServer = connectedServer();
345 // Step 1. empty group on client
346 httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
347 // empty zone to respect defaults
348 if (!"".equals(mclinkSetupServer)) {
349 // Step 2. remove client from server
350 json = "{\"group_id\":\"" + groupId
351 + "\", \"type\":\"remove\", \"client_list\":[\"" + this.host + "\"]}";
352 httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo");
353 // Step 3. reflect changes to master
354 httpResponse = startDistribution(mclinkSetupServer);
355 localDefaultAfterMCLink = getThing().getConfiguration()
356 .get("defaultAfterMCLink").toString();
357 httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host);
358 } else if ("".equals(mclinkSetupServer)) {
359 // fallback in case client is removed from group by ending group on server side
360 localDefaultAfterMCLink = getThing().getConfiguration()
361 .get("defaultAfterMCLink").toString();
362 httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host);
366 } else if ("link".equals(action)) {
367 if (localRole != null) {
368 if ("none".equals(localRole)) {
369 json = "{\"group_id\":\"" + groupId + "\", \"zone\":\"" + mclinkSetupZone
370 + "\", \"type\":\"add\", \"client_list\":[\"" + this.host + "\"]}";
371 logger.trace("setServerInfo json: {}", json);
372 httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo");
373 // All zones of Model are required for MC Link
375 for (int i = 1; i <= zoneNum; i++) {
378 tmpString = "\"main\"";
381 tmpString = tmpString + ", \"zone2\"";
384 tmpString = tmpString + ", \"zone3\"";
387 tmpString = tmpString + ", \"zone4\"";
391 json = "{\"group_id\":\"" + groupId + "\", \"zone\":[" + tmpString + "]}";
392 logger.trace("setClientInfo json: {}", json);
393 httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
394 httpResponse = startDistribution(mclinkSetupServer);
399 updateMCLinkStatus();
401 case CHANNEL_RECALLSCENE:
402 recallScene(command.toString(), zone, this.host);
405 setRepeat(command.toString(), this.host);
407 case CHANNEL_SHUFFLE:
408 setShuffle(command.toString(), this.host);
410 } // END Switch Channel
415 public void initialize() {
416 String localHost = "";
417 thingLabel = thing.getLabel();
418 updateStatus(ThingStatus.UNKNOWN);
419 localHost = getThing().getConfiguration().get("host").toString();
420 this.host = localHost;
421 if (!"".equals(this.host)) {
422 zoneNum = getNumberOfZones(this.host);
423 logger.trace("Zones found: {} - {}", zoneNum, thingLabel);
427 generalHousekeepingTask = scheduler.scheduleWithFixedDelay(this::generalHousekeeping, 5, 300,
429 updateStatus(ThingStatus.ONLINE);
431 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "No host found");
436 private void generalHousekeeping() {
437 thingLabel = thing.getLabel();
438 logger.trace("YXC - Start Keep Alive UDP events (5 minutes - {}) ", thingLabel);
439 keepUdpEventsAlive(this.host);
440 fillOptionsForMCLink();
441 updateMCLinkStatus();
444 private void refreshOnStartup() {
445 for (int i = 1; i <= zoneNum; i++) {
448 createChannels("main");
449 updateStatusZone("main");
452 createChannels("zone2");
453 updateStatusZone("zone2");
456 createChannels("zone3");
457 updateStatusZone("zone3");
460 createChannels("zone4");
461 updateStatusZone("zone4");
466 updateNetUSBPlayer();
467 fillOptionsForMCLink();
468 updateMCLinkStatus();
472 public void dispose() {
473 ScheduledFuture<?> localGeneralHousekeepingTask = generalHousekeepingTask;
474 if (localGeneralHousekeepingTask != null) {
475 localGeneralHousekeepingTask.cancel(true);
481 private void createChannels(String zone) {
482 createChannel(zone, CHANNEL_POWER, CHANNEL_TYPE_UID_POWER, "Switch");
483 createChannel(zone, CHANNEL_MUTE, CHANNEL_TYPE_UID_MUTE, "Switch");
484 createChannel(zone, CHANNEL_VOLUME, CHANNEL_TYPE_UID_VOLUME, "Dimmer");
485 createChannel(zone, CHANNEL_VOLUMEABS, CHANNEL_TYPE_UID_VOLUMEABS, "Number");
486 createChannel(zone, CHANNEL_VOLUMEDB, CHANNEL_TYPE_UID_VOLUMEDB, "Number:Dimensionless");
487 createChannel(zone, CHANNEL_INPUT, CHANNEL_TYPE_UID_INPUT, "String");
488 createChannel(zone, CHANNEL_SOUNDPROGRAM, CHANNEL_TYPE_UID_SOUNDPROGRAM, "String");
489 createChannel(zone, CHANNEL_SLEEP, CHANNEL_TYPE_UID_SLEEP, "Number");
490 createChannel(zone, CHANNEL_SELECTPRESET, CHANNEL_TYPE_UID_SELECTPRESET, "String");
491 createChannel(zone, CHANNEL_RECALLSCENE, CHANNEL_TYPE_UID_RECALLSCENE, "Number");
492 createChannel(zone, CHANNEL_MCLINKSTATUS, CHANNEL_TYPE_UID_MCLINKSTATUS, "String");
495 private void createChannel(String zone, String channel, ChannelTypeUID channelTypeUID, String itemType) {
496 ChannelUID channelToCheck = new ChannelUID(thing.getUID(), zone, channel);
497 if (thing.getChannel(channelToCheck) == null) {
498 ThingBuilder thingBuilder = editThing();
499 Channel testchannel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), zone, channel), itemType)
500 .withType(channelTypeUID).build();
501 thingBuilder.withChannel(testchannel);
502 updateThing(thingBuilder.build());
506 private void powerOffCleanup() {
508 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST);
509 updateState(channel, StringType.valueOf("-"));
510 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK);
511 updateState(channel, StringType.valueOf("-"));
512 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM);
513 updateState(channel, StringType.valueOf("-"));
516 public void processUDPEvent(String json, String trackingID) {
517 logger.trace("UDP package: {} (Tracking: {})", json, trackingID);
519 UdpMessage targetObject = gson.fromJson(json, UdpMessage.class);
520 if (targetObject != null) {
521 if (Objects.nonNull(targetObject.getMain())) {
522 updateStateFromUDPEvent("main", targetObject);
524 if (Objects.nonNull(targetObject.getZone2())) {
525 updateStateFromUDPEvent("zone2", targetObject);
527 if (Objects.nonNull(targetObject.getZone3())) {
528 updateStateFromUDPEvent("zone3", targetObject);
530 if (Objects.nonNull(targetObject.getZone4())) {
531 updateStateFromUDPEvent("zone4", targetObject);
533 if (Objects.nonNull(targetObject.getNetUSB())) {
534 updateStateFromUDPEvent("netusb", targetObject);
536 if (Objects.nonNull(targetObject.getDist())) {
537 updateStateFromUDPEvent("dist", targetObject);
542 private void updateStateFromUDPEvent(String zoneToUpdate, UdpMessage targetObject) {
544 String playInfoUpdated = "";
545 String statusUpdated = "";
546 String powerState = "";
547 String muteState = "";
548 String inputState = "";
550 ActualVolume actualVolume = null;
551 int presetNumber = 0;
553 String distInfoUpdated = "";
554 logger.trace("Handling UDP for {}", zoneToUpdate);
555 switch (zoneToUpdate) {
557 powerState = targetObject.getMain().getPower();
558 muteState = targetObject.getMain().getMute();
559 inputState = targetObject.getMain().getInput();
560 volumeState = targetObject.getMain().getVolume();
561 actualVolume = targetObject.getMain().getActualVolume();
562 statusUpdated = targetObject.getMain().getstatusUpdated();
565 powerState = targetObject.getZone2().getPower();
566 muteState = targetObject.getZone2().getMute();
567 inputState = targetObject.getZone2().getInput();
568 volumeState = targetObject.getZone2().getVolume();
569 actualVolume = targetObject.getZone2().getActualVolume();
570 statusUpdated = targetObject.getZone2().getstatusUpdated();
573 powerState = targetObject.getZone3().getPower();
574 muteState = targetObject.getZone3().getMute();
575 inputState = targetObject.getZone3().getInput();
576 volumeState = targetObject.getZone3().getVolume();
577 actualVolume = targetObject.getZone3().getActualVolume();
578 statusUpdated = targetObject.getZone3().getstatusUpdated();
581 powerState = targetObject.getZone4().getPower();
582 muteState = targetObject.getZone4().getMute();
583 inputState = targetObject.getZone4().getInput();
584 volumeState = targetObject.getZone4().getVolume();
585 actualVolume = targetObject.getZone4().getActualVolume();
586 statusUpdated = targetObject.getZone4().getstatusUpdated();
589 if (Objects.isNull(targetObject.getNetUSB().getPresetControl())) {
592 presetNumber = targetObject.getNetUSB().getPresetControl().getNum();
594 playInfoUpdated = targetObject.getNetUSB().getPlayInfoUpdated();
595 playTime = targetObject.getNetUSB().getPlayTime();
596 // totalTime is not in UDP event
599 distInfoUpdated = targetObject.getDist().getDistInfoUpdated();
603 if (logger.isTraceEnabled()) {
604 logger.trace("{} - Response: {}", zoneToUpdate, responseCode);
605 logger.trace("{} - Power: {}", zoneToUpdate, powerState);
606 logger.trace("{} - Mute: {}", zoneToUpdate, muteState);
607 logger.trace("{} - Volume: {}", zoneToUpdate, volumeState);
608 logger.trace("{} - Volume in dB: {}", zoneToUpdate, (actualVolume != null) ? actualVolume.getValue() : "");
609 logger.trace("{} - Max Volume: {}", zoneToUpdate, maxVolumeState);
610 logger.trace("{} - Input: {}", zoneToUpdate, inputState);
611 logger.trace("{} - Soundprogram: {}", zoneToUpdate, soundProgramState);
612 logger.trace("{} - Sleep: {}", zoneToUpdate, sleepState);
615 if (!powerState.isEmpty()) {
616 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_POWER);
617 if ("on".equals(powerState)) {
618 updateState(channel, OnOffType.ON);
619 } else if ("standby".equals(powerState)) {
620 updateState(channel, OnOffType.OFF);
625 if (!muteState.isEmpty()) {
626 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_MUTE);
627 if ("true".equals(muteState)) {
628 updateState(channel, OnOffType.ON);
629 } else if ("false".equals(muteState)) {
630 updateState(channel, OnOffType.OFF);
634 if (!inputState.isEmpty()) {
635 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_INPUT);
636 updateState(channel, StringType.valueOf(inputState));
639 if (volumeState != 0) {
640 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUME);
641 updateState(channel, new PercentType((volumeState * 100) / maxVolumeState));
642 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUMEABS);
643 updateState(channel, new DecimalType(volumeState));
646 if (actualVolume != null) {
647 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUMEDB);
648 updateState(channel, new QuantityType<>(actualVolume.getValue(), Units.DECIBEL));
651 if (presetNumber != 0) {
652 logger.trace("Preset detected: {}", presetNumber);
653 updatePresets(presetNumber);
656 if ("true".equals(playInfoUpdated)) {
657 updateNetUSBPlayer();
660 if (!statusUpdated.isEmpty()) {
661 updateStatusZone(zoneToUpdate);
664 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME);
665 updateState(channel, StringType.valueOf(String.valueOf(playTime)));
667 if ("true".equals(distInfoUpdated)) {
668 updateMCLinkStatus();
672 private void updateStatusZone(String zoneToUpdate) {
673 String localZone = "";
674 tmpString = getStatus(this.host, zoneToUpdate);
676 Status targetObject = gson.fromJson(tmpString, Status.class);
677 if (targetObject != null) {
678 String responseCode = targetObject.getResponseCode();
679 String powerState = targetObject.getPower();
680 String muteState = targetObject.getMute();
681 volumeState = targetObject.getVolume();
682 ActualVolume actualVolume = targetObject.getActualVolume();
683 maxVolumeState = targetObject.getMaxVolume();
684 inputState = targetObject.getInput();
685 soundProgramState = targetObject.getSoundProgram();
686 sleepState = targetObject.getSleep();
688 if (logger.isTraceEnabled()) {
689 logger.trace("{} - Response: {}", zoneToUpdate, responseCode);
690 logger.trace("{} - Power: {}", zoneToUpdate, powerState);
691 logger.trace("{} - Mute: {}", zoneToUpdate, muteState);
692 logger.trace("{} - Volume: {}", zoneToUpdate, volumeState);
693 logger.trace("{} - Volume in dB: {}", zoneToUpdate,
694 (actualVolume != null) ? actualVolume.getValue() : "");
695 logger.trace("{} - Max Volume: {}", zoneToUpdate, maxVolumeState);
696 logger.trace("{} - Input: {}", zoneToUpdate, inputState);
697 logger.trace("{} - Soundprogram: {}", zoneToUpdate, soundProgramState);
698 logger.trace("{} - Sleep: {}", zoneToUpdate, sleepState);
701 switch (responseCode) {
703 for (Channel channel : getThing().getChannels()) {
704 ChannelUID channelUID = channel.getUID();
705 channelWithoutGroup = channelUID.getIdWithoutGroup();
706 localZone = channelUID.getGroupId();
707 if (localZone != null) {
708 if (isLinked(channelUID)) {
709 switch (channelWithoutGroup) {
711 if ("on".equals(powerState)) {
712 if (localZone.equals(zoneToUpdate)) {
713 updateState(channelUID, OnOffType.ON);
715 } else if ("standby".equals(powerState)) {
716 if (localZone.equals(zoneToUpdate)) {
717 updateState(channelUID, OnOffType.OFF);
722 if ("true".equals(muteState)) {
723 if (localZone.equals(zoneToUpdate)) {
724 updateState(channelUID, OnOffType.ON);
726 } else if ("false".equals(muteState)) {
727 if (localZone.equals(zoneToUpdate)) {
728 updateState(channelUID, OnOffType.OFF);
733 if (localZone.equals(zoneToUpdate)) {
734 updateState(channelUID,
735 new PercentType((volumeState * 100) / maxVolumeState));
738 case CHANNEL_VOLUMEABS:
739 if (localZone.equals(zoneToUpdate)) {
740 updateState(channelUID, new DecimalType(volumeState));
743 case CHANNEL_VOLUMEDB:
744 if (localZone.equals(zoneToUpdate)) {
745 if (actualVolume != null) {
746 updateState(channelUID,
747 new QuantityType<>(actualVolume.getValue(), Units.DECIBEL));
749 updateState(channelUID, UnDefType.UNDEF);
754 if (localZone.equals(zoneToUpdate)) {
755 updateState(channelUID, StringType.valueOf(inputState));
758 case CHANNEL_SOUNDPROGRAM:
759 if (localZone.equals(zoneToUpdate)) {
760 updateState(channelUID, StringType.valueOf(soundProgramState));
764 if (localZone.equals(zoneToUpdate)) {
765 updateState(channelUID, new DecimalType(sleepState));
768 } // END switch (channelWithoutGroup)
774 logger.trace("Nothing to do! - {} ({})", thingLabel, zoneToUpdate);
780 private void updatePresets(int value) {
781 String inputText = "";
782 int presetCounter = 0;
783 int currentPreset = 0;
784 tmpString = getPresetInfo(this.host);
786 PresetInfo presetinfo = gson.fromJson(tmpString, PresetInfo.class);
787 if (presetinfo != null) {
788 String responseCode = presetinfo.getResponseCode();
789 if ("0".equals(responseCode)) {
790 List<StateOption> optionsPresets = new ArrayList<>();
791 inputText = getLastInput();
792 if (inputText != null) {
793 for (JsonElement pr : presetinfo.getPresetInfo()) {
794 presetCounter = presetCounter + 1;
795 JsonObject presetObject = pr.getAsJsonObject();
796 String text = presetObject.get("text").getAsString();
797 if (!"".equals(text)) {
798 optionsPresets.add(new StateOption(String.valueOf(presetCounter),
799 "#" + String.valueOf(presetCounter) + " " + text));
800 if (inputText.equals(text)) {
801 currentPreset = presetCounter;
807 currentPreset = value;
809 for (Channel channel : getThing().getChannels()) {
810 ChannelUID channelUID = channel.getUID();
811 channelWithoutGroup = channelUID.getIdWithoutGroup();
812 if (isLinked(channelUID)) {
813 switch (channelWithoutGroup) {
814 case CHANNEL_SELECTPRESET:
815 stateDescriptionProvider.setStateOptions(channelUID, optionsPresets);
816 updateState(channelUID, StringType.valueOf(String.valueOf(currentPreset)));
825 private void updateNetUSBPlayer() {
826 tmpString = getPlayInfo(this.host);
829 PlayInfo targetObject = gson.fromJson(tmpString, PlayInfo.class);
830 if (targetObject != null) {
831 String responseCode = targetObject.getResponseCode();
832 String playbackState = targetObject.getPlayback();
833 artistState = targetObject.getArtist();
834 trackState = targetObject.getTrack();
835 albumState = targetObject.getAlbum();
836 String albumArtUrlState = targetObject.getAlbumArtUrl();
837 repeatState = targetObject.getRepeat();
838 shuffleState = targetObject.getShuffle();
839 playTimeState = targetObject.getPlayTime();
840 totalTimeState = targetObject.getTotalTime();
842 if ("0".equals(responseCode)) {
843 ChannelUID testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYER);
844 switch (playbackState) {
846 updateState(testchannel, PlayPauseType.PLAY);
849 updateState(testchannel, PlayPauseType.PAUSE);
852 updateState(testchannel, PlayPauseType.PAUSE);
855 updateState(testchannel, RewindFastforwardType.REWIND);
858 updateState(testchannel, RewindFastforwardType.FASTFORWARD);
861 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST);
862 updateState(testchannel, StringType.valueOf(artistState));
863 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK);
864 updateState(testchannel, StringType.valueOf(trackState));
865 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM);
866 updateState(testchannel, StringType.valueOf(albumState));
867 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUMART);
868 if (!"".equals(albumArtUrlState)) {
869 albumArtUrlState = HTTP + this.host + albumArtUrlState;
871 updateState(testchannel, StringType.valueOf(albumArtUrlState));
872 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_REPEAT);
873 updateState(testchannel, StringType.valueOf(repeatState));
874 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_SHUFFLE);
875 updateState(testchannel, StringType.valueOf(shuffleState));
876 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME);
877 updateState(testchannel, StringType.valueOf(String.valueOf(playTimeState)));
878 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TOTALTIME);
879 updateState(testchannel, StringType.valueOf(String.valueOf(totalTimeState)));
884 private @Nullable String getLastInput() {
886 tmpString = getRecentInfo(this.host);
887 RecentInfo recentinfo = gson.fromJson(tmpString, RecentInfo.class);
888 if (recentinfo != null) {
889 String responseCode = recentinfo.getResponseCode();
890 if ("0".equals(responseCode)) {
891 for (JsonElement ri : recentinfo.getRecentInfo()) {
892 JsonObject recentObject = ri.getAsJsonObject();
893 text = recentObject.get("text").getAsString();
901 private String connectedServer() {
902 DistributionInfo distributioninfo = new DistributionInfo();
903 Bridge bridge = getBridge();
904 String remotehost = "";
906 String localHost = "";
907 if (bridge != null) {
908 for (Thing thing : bridge.getThings()) {
909 remotehost = thing.getConfiguration().get("host").toString();
910 tmpString = getDistributionInfo(remotehost);
911 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
912 if (distributioninfo != null) {
913 String localRole = distributioninfo.getRole();
914 if ("server".equals(localRole)) {
915 for (JsonElement ip : distributioninfo.getClientList()) {
916 JsonObject clientObject = ip.getAsJsonObject();
917 localHost = getThing().getConfiguration().get("host").toString();
918 if (localHost.equals(clientObject.get("ip_address").getAsString())) {
930 private void fillOptionsForMCLink() {
931 Bridge bridge = getBridge();
934 int zonesPerHost = 1;
936 tmpString = getDistributionInfo(this.host);
937 DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class);
938 if (targetObject != null) {
939 clients = targetObject.getClientList().size();
942 List<StateOption> options = new ArrayList<>();
943 // first add 3 options for MC Link
944 options.add(new StateOption("", "Standalone"));
945 options.add(new StateOption("server", "Server: " + clients + " clients"));
946 options.add(new StateOption("client", "Client"));
948 if (bridge != null) {
949 for (Thing thing : bridge.getThings()) {
950 label = thing.getLabel();
951 host = thing.getConfiguration().get("host").toString();
952 logger.trace("Thing found on Bridge: {} - {}", label, host);
953 zonesPerHost = getNumberOfZones(host);
954 for (int i = 1; i <= zonesPerHost; i++) {
957 options.add(new StateOption(host + "***main", label + " - main (" + host + ")"));
960 options.add(new StateOption(host + "***zone2", label + " - zone2 (" + host + ")"));
963 options.add(new StateOption(host + "***zone3", label + " - zone3 (" + host + ")"));
966 options.add(new StateOption(host + "***zone4", label + " - zone4 (" + host + ")"));
973 // for each zone of the device, set all the possible combinations
974 ChannelUID testchannel;
975 for (int i = 1; i <= zoneNum; i++) {
978 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
979 if (isLinked(testchannel)) {
980 stateDescriptionProvider.setStateOptions(testchannel, options);
984 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
985 if (isLinked(testchannel)) {
986 stateDescriptionProvider.setStateOptions(testchannel, options);
990 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
991 if (isLinked(testchannel)) {
992 stateDescriptionProvider.setStateOptions(testchannel, options);
996 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
997 if (isLinked(testchannel)) {
998 stateDescriptionProvider.setStateOptions(testchannel, options);
1005 private String generateGroupId() {
1006 return UUID.randomUUID().toString().replace("-", "").substring(0, 32);
1009 private int getNumberOfZones(@Nullable String host) {
1010 int numberOfZones = 0;
1011 tmpString = getFeatures(host);
1013 Features targetObject = gson.fromJson(tmpString, Features.class);
1014 if (targetObject != null) {
1015 responseCode = targetObject.getResponseCode();
1016 if ("0".equals(responseCode)) {
1017 numberOfZones = targetObject.getSystem().getZoneNum();
1020 return numberOfZones;
1023 public @Nullable String getDeviceId() {
1024 tmpString = getDeviceInfo(this.host);
1025 String localValueToCheck = "";
1027 DeviceInfo targetObject = gson.fromJson(tmpString, DeviceInfo.class);
1028 if (targetObject != null) {
1029 localValueToCheck = targetObject.getDeviceId();
1031 return localValueToCheck;
1034 private void setVolumeLinkedDevice(int value, @Nullable String zone, String host) {
1035 logger.trace("setVolumeLinkedDevice: {}", host);
1036 int zoneNumLinkedDevice = getNumberOfZones(host);
1037 int maxVolumeLinkedDevice = 0;
1039 Status targetObject = new Status();
1041 for (int i = 1; i <= zoneNumLinkedDevice; i++) {
1044 tmpString = getStatus(host, "main");
1045 targetObject = gson.fromJson(tmpString, Status.class);
1046 if (targetObject != null) {
1047 responseCode = targetObject.getResponseCode();
1048 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1049 newVolume = maxVolumeLinkedDevice * value / 100;
1050 setVolume(newVolume, "main", host);
1054 tmpString = getStatus(host, "zone2");
1055 targetObject = gson.fromJson(tmpString, Status.class);
1056 if (targetObject != null) {
1057 responseCode = targetObject.getResponseCode();
1058 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1059 newVolume = maxVolumeLinkedDevice * value / 100;
1060 setVolume(newVolume, "zone2", host);
1064 tmpString = getStatus(host, "zone3");
1065 targetObject = gson.fromJson(tmpString, Status.class);
1066 if (targetObject != null) {
1067 responseCode = targetObject.getResponseCode();
1068 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1069 newVolume = maxVolumeLinkedDevice * value / 100;
1070 setVolume(newVolume, "zone3", host);
1074 tmpString = getStatus(host, "zone4");
1075 targetObject = gson.fromJson(tmpString, Status.class);
1076 if (targetObject != null) {
1077 responseCode = targetObject.getResponseCode();
1078 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1079 newVolume = maxVolumeLinkedDevice * value / 100;
1080 setVolume(newVolume, "zone4", host);
1087 private void setVolumeDbLinkedDevice(float value, @Nullable String zone, String host) {
1088 logger.trace("setVolumeDbLinkedDevice: {}", host);
1089 int zoneNumLinkedDevice = getNumberOfZones(host);
1090 for (int i = 1; i <= zoneNumLinkedDevice; i++) {
1093 setVolumeDb(value, "main", host);
1096 setVolumeDb(value, "zone2", host);
1099 setVolumeDb(value, "zone3", host);
1102 setVolumeDb(value, "zone4", host);
1108 public void updateMCLinkStatus() {
1109 tmpString = getDistributionInfo(this.host);
1111 DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class);
1112 if (targetObject != null) {
1113 String localRole = targetObject.getRole();
1114 groupId = targetObject.getGroupId();
1115 switch (localRole) {
1117 setMCLinkToStandalone();
1120 setMCLinkToServer();
1123 setMCLinkToClient();
1129 private void setMCLinkToStandalone() {
1130 ChannelUID testchannel;
1131 for (int i = 1; i <= zoneNum; i++) {
1134 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1135 updateState(testchannel, StringType.valueOf(""));
1138 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1139 updateState(testchannel, StringType.valueOf(""));
1142 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1143 updateState(testchannel, StringType.valueOf(""));
1146 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1147 updateState(testchannel, StringType.valueOf(""));
1153 private void setMCLinkToClient() {
1154 ChannelUID testchannel;
1155 for (int i = 1; i <= zoneNum; i++) {
1158 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1159 updateState(testchannel, StringType.valueOf("client"));
1162 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1163 updateState(testchannel, StringType.valueOf("client"));
1166 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1167 updateState(testchannel, StringType.valueOf("client"));
1170 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1171 updateState(testchannel, StringType.valueOf("client"));
1177 private void setMCLinkToServer() {
1178 ChannelUID testchannel;
1179 for (int i = 1; i <= zoneNum; i++) {
1182 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1183 updateState(testchannel, StringType.valueOf("server"));
1186 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1187 updateState(testchannel, StringType.valueOf("server"));
1190 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1191 updateState(testchannel, StringType.valueOf("server"));
1194 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1195 updateState(testchannel, StringType.valueOf("server"));
1201 private String makeRequest(@Nullable String topicAVR, String url) {
1202 String response = "";
1204 response = HttpUtil.executeUrl("GET", HTTP + url, LONG_CONNECTION_TIMEOUT_MILLISEC);
1205 logger.trace("{} - {}", topicAVR, response);
1207 } catch (IOException e) {
1208 logger.trace("IO Exception - {} - {}", topicAVR, e.getMessage());
1209 return "{\"response_code\":\"999\"}";
1212 // End Various functions
1216 // Start Zone Related
1218 private @Nullable String getStatus(@Nullable String host, String zone) {
1219 return makeRequest("Status", host + YAMAHA_EXTENDED_CONTROL + zone + "/getStatus");
1222 private @Nullable String setPower(String value, @Nullable String zone, @Nullable String host) {
1223 return makeRequest("Power", host + YAMAHA_EXTENDED_CONTROL + zone + "/setPower?power=" + value);
1226 private @Nullable String setMute(String value, @Nullable String zone, @Nullable String host) {
1227 return makeRequest("Mute", host + YAMAHA_EXTENDED_CONTROL + zone + "/setMute?enable=" + value);
1230 private @Nullable String setVolume(int value, @Nullable String zone, @Nullable String host) {
1231 return makeRequest("Volume", host + YAMAHA_EXTENDED_CONTROL + zone + "/setVolume?volume=" + value);
1235 * Sets the volume in decibels (dB).
1237 * @param value volume in dB (decibels)
1238 * @param zone name of zone
1239 * @param host hostname or ip address
1240 * @return HTTP request
1242 private @Nullable String setVolumeDb(float value, @Nullable String zone, @Nullable String host) {
1243 float volumeDbMin = Float.parseFloat(getThing().getConfiguration().get("volumeDbMin").toString());
1244 float volumeDbMax = Float.parseFloat(getThing().getConfiguration().get("volumeDbMax").toString());
1245 if (value < volumeDbMin) {
1246 value = volumeDbMin;
1248 if (value > volumeDbMax) {
1249 value = volumeDbMax;
1252 // Yamaha accepts only integer values with .0 or .5 at the end only (-20.5dB, -20.0dB) - at least on RX-S601D.
1253 // The order matters here. We want to cast to integer first and then scale by 10.
1254 // Effectively we're only allowing dB values with .0 at the end.
1255 logger.trace("setVolumeDb: {} dB", value);
1256 return makeRequest("Volume", host + YAMAHA_EXTENDED_CONTROL + zone + "/setActualVolume?mode=db&value=" + value);
1259 private @Nullable String setInput(String value, @Nullable String zone, @Nullable String host) {
1260 return makeRequest("setInput", host + YAMAHA_EXTENDED_CONTROL + zone + "/setInput?input=" + value);
1263 private @Nullable String setSoundProgram(String value, @Nullable String zone, @Nullable String host) {
1264 return makeRequest("setSoundProgram",
1265 host + YAMAHA_EXTENDED_CONTROL + zone + "/setSoundProgram?program=" + value);
1268 private @Nullable String setPreset(String value, @Nullable String zone, @Nullable String host) {
1269 return makeRequest("setPreset",
1270 host + YAMAHA_EXTENDED_CONTROL + "netusb/recallPreset?zone=" + zone + "&num=" + value);
1273 private @Nullable String setSleep(String value, @Nullable String zone, @Nullable String host) {
1274 return makeRequest("setSleep", host + YAMAHA_EXTENDED_CONTROL + zone + "/setSleep?sleep=" + value);
1277 private @Nullable String recallScene(String value, @Nullable String zone, @Nullable String host) {
1278 return makeRequest("recallScene", host + YAMAHA_EXTENDED_CONTROL + zone + "/recallScene?num=" + value);
1282 // Start Net Radio/USB Related
1284 private @Nullable String getPresetInfo(@Nullable String host) {
1285 return makeRequest("PresetInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPresetInfo");
1288 private @Nullable String getRecentInfo(@Nullable String host) {
1289 return makeRequest("RecentInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getRecentInfo");
1292 private @Nullable String getPlayInfo(@Nullable String host) {
1293 return makeRequest("PlayInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo");
1296 private @Nullable String setPlayback(String value, @Nullable String host) {
1297 return makeRequest("Playback", host + YAMAHA_EXTENDED_CONTROL + "netusb/setPlayback?playback=" + value);
1300 private @Nullable String setRepeat(String value, @Nullable String host) {
1301 return makeRequest("Repeat", host + YAMAHA_EXTENDED_CONTROL + "netusb/setRepeat?mode=" + value);
1304 private @Nullable String setShuffle(String value, @Nullable String host) {
1305 return makeRequest("Shuffle", host + YAMAHA_EXTENDED_CONTROL + "netusb/setShuffle?mode=" + value);
1308 // End Net Radio/USB Related
1310 // Start Music Cast API calls
1311 private @Nullable String getDistributionInfo(@Nullable String host) {
1312 return makeRequest("DistributionInfo", host + YAMAHA_EXTENDED_CONTROL + "dist/getDistributionInfo");
1315 private @Nullable String setClientServerInfo(@Nullable String host, String json, String type) {
1316 InputStream is = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
1318 url = "http://" + host + YAMAHA_EXTENDED_CONTROL + "dist/" + type;
1319 httpResponse = HttpUtil.executeUrl("POST", url, is, "", LONG_CONNECTION_TIMEOUT_MILLISEC);
1320 logger.trace("MC Link/Unlink Client {}", httpResponse);
1321 return httpResponse;
1322 } catch (IOException e) {
1323 logger.trace("IO Exception - {} - {}", type, e.getMessage());
1324 return "{\"response_code\":\"999\"}";
1328 private @Nullable String startDistribution(@Nullable String host) {
1329 Random ran = new Random();
1330 int nxt = ran.nextInt(200000);
1331 return makeRequest("StartDistribution", host + YAMAHA_EXTENDED_CONTROL + "dist/startDistribution?num=" + nxt);
1334 // End Music Cast API calls
1336 // Start General/System API calls
1338 private @Nullable String getFeatures(@Nullable String host) {
1339 return makeRequest("Features", host + YAMAHA_EXTENDED_CONTROL + "system/getFeatures");
1342 private @Nullable String getDeviceInfo(@Nullable String host) {
1343 return makeRequest("DeviceInfo", host + YAMAHA_EXTENDED_CONTROL + "system/getDeviceInfo");
1346 private void keepUdpEventsAlive(@Nullable String host) {
1347 Properties appProps = new Properties();
1348 appProps.setProperty("X-AppName", "MusicCast/1");
1349 appProps.setProperty("X-AppPort", "41100");
1351 httpResponse = HttpUtil.executeUrl("GET", HTTP + host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo",
1352 appProps, null, "", LONG_CONNECTION_TIMEOUT_MILLISEC);
1353 // logger.trace("{}", httpResponse);
1354 logger.trace("{} - {}", "UDP task", httpResponse);
1355 } catch (IOException e) {
1356 logger.trace("UDP refresh failed - {}", e.getMessage());
1359 // End General/System API calls