2 * Copyright (c) 2010-2022 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.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.QuantityType;
48 import org.openhab.core.library.types.RewindFastforwardType;
49 import org.openhab.core.library.types.StringType;
50 import org.openhab.core.library.unit.Units;
51 import org.openhab.core.thing.Bridge;
52 import org.openhab.core.thing.Channel;
53 import org.openhab.core.thing.ChannelUID;
54 import org.openhab.core.thing.Thing;
55 import org.openhab.core.thing.ThingStatus;
56 import org.openhab.core.thing.ThingStatusDetail;
57 import org.openhab.core.thing.binding.BaseThingHandler;
58 import org.openhab.core.thing.binding.builder.ChannelBuilder;
59 import org.openhab.core.thing.binding.builder.ThingBuilder;
60 import org.openhab.core.thing.type.ChannelTypeUID;
61 import org.openhab.core.types.Command;
62 import org.openhab.core.types.RefreshType;
63 import org.openhab.core.types.StateOption;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
67 import com.google.gson.Gson;
68 import com.google.gson.JsonElement;
69 import com.google.gson.JsonObject;
72 * The {@link YamahaMusiccastHandler} is responsible for handling commands, which are
73 * sent to one of the channels.
75 * @author Lennert Coopman - Initial contribution
76 * @author Florian Hotze - Add volume in decibel
79 public class YamahaMusiccastHandler extends BaseThingHandler {
80 private Gson gson = new Gson();
81 private Logger logger = LoggerFactory.getLogger(YamahaMusiccastHandler.class);
82 private @Nullable ScheduledFuture<?> generalHousekeepingTask;
83 private @Nullable String httpResponse;
84 private @Nullable String tmpString = "";
85 private int volumePercent = 0;
86 private int volumeAbsValue = 0;
87 private @Nullable String responseCode = "";
88 private int volumeState = 0;
89 private float volumeDbState = -80f; // -80.0 dB
90 private int maxVolumeState = 0;
91 private @Nullable String inputState = "";
92 private @Nullable String soundProgramState = "";
93 private int sleepState = 0;
94 private @Nullable String artistState = "";
95 private @Nullable String trackState = "";
96 private @Nullable String albumState = "";
97 private @Nullable String repeatState = "";
98 private @Nullable String shuffleState = "";
99 private int playTimeState = 0;
100 private int totalTimeState = 0;
101 private @Nullable String zone = "main";
102 private String channelWithoutGroup = "";
103 private @Nullable String thingLabel = "";
104 private @Nullable String mclinkSetupServer = "";
105 private @Nullable String mclinkSetupZone = "";
106 private String url = "";
107 private String json = "";
108 private String action = "";
109 private int zoneNum = 0;
110 private @Nullable String groupId = "";
111 private @Nullable String host;
112 public @Nullable String deviceId = "";
114 private YamahaMusiccastStateDescriptionProvider stateDescriptionProvider;
116 public YamahaMusiccastHandler(Thing thing, YamahaMusiccastStateDescriptionProvider stateDescriptionProvider) {
118 this.stateDescriptionProvider = stateDescriptionProvider;
122 public void handleCommand(ChannelUID channelUID, Command command) {
123 String localValueToCheck = "";
124 String localRole = "";
125 boolean localSyncVolume;
126 String localDefaultAfterMCLink = "";
127 String localRoleSelectedThing = "";
128 if (command != RefreshType.REFRESH) {
129 logger.trace("Handling command {} for channel {}", command, channelUID);
130 channelWithoutGroup = channelUID.getIdWithoutGroup();
131 zone = channelUID.getGroupId();
132 DistributionInfo distributioninfo = new DistributionInfo();
133 Response response = new Response();
134 switch (channelWithoutGroup) {
136 if (command == OnOffType.ON) {
137 httpResponse = setPower("on", zone, this.host);
138 response = gson.fromJson(httpResponse, Response.class);
139 if (response != null) {
140 localValueToCheck = response.getResponseCode();
141 if (!"0".equals(localValueToCheck)) {
142 updateState(channelUID, OnOffType.OFF);
145 // check on scheduler task for UDP events
146 ScheduledFuture<?> localGeneralHousekeepingTask = generalHousekeepingTask;
147 if (localGeneralHousekeepingTask == null) {
148 logger.trace("YXC - No scheduler task found!");
149 generalHousekeepingTask = scheduler.scheduleWithFixedDelay(this::generalHousekeeping, 5,
150 300, TimeUnit.SECONDS);
153 logger.trace("Scheduler task found!");
156 } else if (command == OnOffType.OFF) {
157 httpResponse = setPower("standby", zone, this.host);
158 response = gson.fromJson(httpResponse, Response.class);
160 if (response != null) {
161 localValueToCheck = response.getResponseCode();
162 if (!"0".equals(localValueToCheck)) {
163 updateState(channelUID, OnOffType.ON);
169 if (command == OnOffType.ON) {
170 httpResponse = setMute("true", zone, this.host);
171 response = gson.fromJson(httpResponse, Response.class);
172 if (response != null) {
173 localValueToCheck = response.getResponseCode();
174 if (!"0".equals(localValueToCheck)) {
175 updateState(channelUID, OnOffType.OFF);
178 } else if (command == OnOffType.OFF) {
179 httpResponse = setMute("false", zone, this.host);
180 response = gson.fromJson(httpResponse, Response.class);
181 if (response != null) {
182 localValueToCheck = response.getResponseCode();
183 if (!"0".equals(localValueToCheck)) {
184 updateState(channelUID, OnOffType.ON);
190 volumePercent = Integer.parseInt(command.toString().replace(".0", ""));
191 volumeAbsValue = (maxVolumeState * volumePercent) / 100;
192 setVolume(volumeAbsValue, zone, this.host);
193 localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString());
194 if (localSyncVolume == Boolean.TRUE) {
195 tmpString = getDistributionInfo(this.host);
196 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
197 if (distributioninfo != null) {
198 localRole = distributioninfo.getRole();
199 if ("server".equals(localRole)) {
200 for (JsonElement ip : distributioninfo.getClientList()) {
201 JsonObject clientObject = ip.getAsJsonObject();
202 setVolumeLinkedDevice(volumePercent, zone,
203 clientObject.get("ip_address").getAsString());
207 } // END config.syncVolume
209 case CHANNEL_VOLUMEABS:
210 volumeAbsValue = Integer.parseInt(command.toString().replace(".0", ""));
211 volumePercent = (volumeAbsValue / maxVolumeState) * 100;
212 setVolume(volumeAbsValue, zone, this.host);
213 localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString());
214 if (localSyncVolume == Boolean.TRUE) {
215 tmpString = getDistributionInfo(this.host);
216 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
217 if (distributioninfo != null) {
218 localRole = distributioninfo.getRole();
219 if ("server".equals(localRole)) {
220 for (JsonElement ip : distributioninfo.getClientList()) {
221 JsonObject clientObject = ip.getAsJsonObject();
222 setVolumeLinkedDevice(volumePercent, zone,
223 clientObject.get("ip_address").getAsString());
229 case CHANNEL_VOLUMEDB:
230 setVolumeDb(((QuantityType<?>) command).floatValue(), zone, this.host);
231 localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString());
232 if (localSyncVolume == Boolean.TRUE) {
233 tmpString = getDistributionInfo(this.host);
234 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
235 if (distributioninfo != null) {
236 localRole = distributioninfo.getRole();
237 if ("server".equals(localRole)) {
238 for (JsonElement ip : distributioninfo.getClientList()) {
239 JsonObject clientObject = ip.getAsJsonObject();
240 setVolumeDbLinkedDevice(((DecimalType) command).floatValue(), zone,
241 clientObject.get("ip_address").getAsString());
248 // if it is a client, disconnect it first.
249 tmpString = getDistributionInfo(this.host);
250 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
251 if (distributioninfo != null) {
252 localRole = distributioninfo.getRole();
253 if ("client".equals(localRole)) {
254 json = "{\"group_id\":\"\"}";
255 httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
258 setInput(command.toString(), zone, this.host);
260 case CHANNEL_SOUNDPROGRAM:
261 setSoundProgram(command.toString(), zone, this.host);
263 case CHANNEL_SELECTPRESET:
264 setPreset(command.toString(), zone, this.host);
267 if (command.equals(PlayPauseType.PLAY)) {
268 setPlayback("play", this.host);
269 } else if (command.equals(PlayPauseType.PAUSE)) {
270 setPlayback("pause", this.host);
271 } else if (command.equals(NextPreviousType.NEXT)) {
272 setPlayback("next", this.host);
273 } else if (command.equals(NextPreviousType.PREVIOUS)) {
274 setPlayback("previous", this.host);
275 } else if (command.equals(RewindFastforwardType.REWIND)) {
276 setPlayback("fast_reverse_start", this.host);
277 } else if (command.equals(RewindFastforwardType.FASTFORWARD)) {
278 setPlayback("fast_forward_end", this.host);
282 setSleep(command.toString(), zone, this.host);
284 case CHANNEL_MCLINKSTATUS:
287 tmpString = getDistributionInfo(this.host);
288 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
289 if (distributioninfo != null) {
290 responseCode = distributioninfo.getResponseCode();
291 localRole = distributioninfo.getRole();
292 if (command.toString().equals("")) {
294 groupId = distributioninfo.getGroupId();
295 } else if (command.toString().contains("***")) {
297 String[] parts = command.toString().split("\\*\\*\\*");
298 if (parts.length > 1) {
299 mclinkSetupServer = parts[0];
300 mclinkSetupZone = parts[1];
301 tmpString = getDistributionInfo(mclinkSetupServer);
302 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
303 if (distributioninfo != null) {
304 responseCode = distributioninfo.getResponseCode();
305 localRoleSelectedThing = distributioninfo.getRole();
306 groupId = distributioninfo.getGroupId();
307 if (localRoleSelectedThing != null) {
308 if ("server".equals(localRoleSelectedThing)) {
309 groupId = distributioninfo.getGroupId();
310 } else if ("client".equals(localRoleSelectedThing)) {
312 } else if ("none".equals(localRoleSelectedThing)) {
313 groupId = generateGroupId();
320 if ("unlink".equals(action)) {
321 json = "{\"group_id\":\"\"}";
322 if (localRole != null) {
323 if ("server".equals(localRole)) {
324 httpResponse = setClientServerInfo(this.host, json, "setServerInfo");
325 // Set GroupId = "" for linked clients
326 if (distributioninfo != null) {
327 for (JsonElement ip : distributioninfo.getClientList()) {
328 JsonObject clientObject = ip.getAsJsonObject();
329 setClientServerInfo(clientObject.get("ip_address").getAsString(), json,
333 } else if ("client".equals(localRole)) {
334 mclinkSetupServer = connectedServer();
335 // Step 1. empty group on client
336 httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
337 // empty zone to respect defaults
338 if (!"".equals(mclinkSetupServer)) {
339 // Step 2. remove client from server
340 json = "{\"group_id\":\"" + groupId
341 + "\", \"type\":\"remove\", \"client_list\":[\"" + this.host + "\"]}";
342 httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo");
343 // Step 3. reflect changes to master
344 httpResponse = startDistribution(mclinkSetupServer);
345 localDefaultAfterMCLink = getThing().getConfiguration()
346 .get("defaultAfterMCLink").toString();
347 httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host);
348 } else if ("".equals(mclinkSetupServer)) {
349 // fallback in case client is removed from group by ending group on server side
350 localDefaultAfterMCLink = getThing().getConfiguration()
351 .get("defaultAfterMCLink").toString();
352 httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host);
356 } else if ("link".equals(action)) {
357 if (localRole != null) {
358 if ("none".equals(localRole)) {
359 json = "{\"group_id\":\"" + groupId + "\", \"zone\":\"" + mclinkSetupZone
360 + "\", \"type\":\"add\", \"client_list\":[\"" + this.host + "\"]}";
361 logger.trace("setServerInfo json: {}", json);
362 httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo");
363 // All zones of Model are required for MC Link
365 for (int i = 1; i <= zoneNum; i++) {
368 tmpString = "\"main\"";
371 tmpString = tmpString + ", \"zone2\"";
374 tmpString = tmpString + ", \"zone3\"";
377 tmpString = tmpString + ", \"zone4\"";
381 json = "{\"group_id\":\"" + groupId + "\", \"zone\":[" + tmpString + "]}";
382 logger.trace("setClientInfo json: {}", json);
383 httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
384 httpResponse = startDistribution(mclinkSetupServer);
389 updateMCLinkStatus();
391 case CHANNEL_RECALLSCENE:
392 recallScene(command.toString(), zone, this.host);
395 setRepeat(command.toString(), this.host);
397 case CHANNEL_SHUFFLE:
398 setShuffle(command.toString(), this.host);
400 } // END Switch Channel
405 public void initialize() {
406 String localHost = "";
407 thingLabel = thing.getLabel();
408 updateStatus(ThingStatus.UNKNOWN);
409 localHost = getThing().getConfiguration().get("host").toString();
410 this.host = localHost;
411 if (!"".equals(this.host)) {
412 zoneNum = getNumberOfZones(this.host);
413 logger.trace("Zones found: {} - {}", zoneNum, thingLabel);
417 generalHousekeepingTask = scheduler.scheduleWithFixedDelay(this::generalHousekeeping, 5, 300,
419 updateStatus(ThingStatus.ONLINE);
421 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "No host found");
426 private void generalHousekeeping() {
427 thingLabel = thing.getLabel();
428 logger.trace("YXC - Start Keep Alive UDP events (5 minutes - {}) ", thingLabel);
429 keepUdpEventsAlive(this.host);
430 fillOptionsForMCLink();
431 updateMCLinkStatus();
434 private void refreshOnStartup() {
435 for (int i = 1; i <= zoneNum; i++) {
438 createChannels("main");
439 updateStatusZone("main");
442 createChannels("zone2");
443 updateStatusZone("zone2");
446 createChannels("zone3");
447 updateStatusZone("zone3");
450 createChannels("zone4");
451 updateStatusZone("zone4");
456 updateNetUSBPlayer();
457 fillOptionsForMCLink();
458 updateMCLinkStatus();
462 public void dispose() {
463 ScheduledFuture<?> localGeneralHousekeepingTask = generalHousekeepingTask;
464 if (localGeneralHousekeepingTask != null) {
465 localGeneralHousekeepingTask.cancel(true);
471 private void createChannels(String zone) {
472 createChannel(zone, CHANNEL_POWER, CHANNEL_TYPE_UID_POWER, "Switch");
473 createChannel(zone, CHANNEL_MUTE, CHANNEL_TYPE_UID_MUTE, "Switch");
474 createChannel(zone, CHANNEL_VOLUME, CHANNEL_TYPE_UID_VOLUME, "Dimmer");
475 createChannel(zone, CHANNEL_VOLUMEABS, CHANNEL_TYPE_UID_VOLUMEABS, "Number");
476 createChannel(zone, CHANNEL_VOLUMEDB, CHANNEL_TYPE_UID_VOLUMEDB, "Number:Dimensionless");
477 createChannel(zone, CHANNEL_INPUT, CHANNEL_TYPE_UID_INPUT, "String");
478 createChannel(zone, CHANNEL_SOUNDPROGRAM, CHANNEL_TYPE_UID_SOUNDPROGRAM, "String");
479 createChannel(zone, CHANNEL_SLEEP, CHANNEL_TYPE_UID_SLEEP, "Number");
480 createChannel(zone, CHANNEL_SELECTPRESET, CHANNEL_TYPE_UID_SELECTPRESET, "String");
481 createChannel(zone, CHANNEL_RECALLSCENE, CHANNEL_TYPE_UID_RECALLSCENE, "Number");
482 createChannel(zone, CHANNEL_MCLINKSTATUS, CHANNEL_TYPE_UID_MCLINKSTATUS, "String");
485 private void createChannel(String zone, String channel, ChannelTypeUID channelTypeUID, String itemType) {
486 ChannelUID channelToCheck = new ChannelUID(thing.getUID(), zone, channel);
487 if (thing.getChannel(channelToCheck) == null) {
488 ThingBuilder thingBuilder = editThing();
489 Channel testchannel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), zone, channel), itemType)
490 .withType(channelTypeUID).build();
491 thingBuilder.withChannel(testchannel);
492 updateThing(thingBuilder.build());
496 private void powerOffCleanup() {
498 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST);
499 updateState(channel, StringType.valueOf("-"));
500 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK);
501 updateState(channel, StringType.valueOf("-"));
502 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM);
503 updateState(channel, StringType.valueOf("-"));
506 public void processUDPEvent(String json, String trackingID) {
507 logger.trace("UDP package: {} (Tracking: {})", json, trackingID);
509 UdpMessage targetObject = gson.fromJson(json, UdpMessage.class);
510 if (targetObject != null) {
511 if (Objects.nonNull(targetObject.getMain())) {
512 updateStateFromUDPEvent("main", targetObject);
514 if (Objects.nonNull(targetObject.getZone2())) {
515 updateStateFromUDPEvent("zone2", targetObject);
517 if (Objects.nonNull(targetObject.getZone3())) {
518 updateStateFromUDPEvent("zone3", targetObject);
520 if (Objects.nonNull(targetObject.getZone4())) {
521 updateStateFromUDPEvent("zone4", targetObject);
523 if (Objects.nonNull(targetObject.getNetUSB())) {
524 updateStateFromUDPEvent("netusb", targetObject);
526 if (Objects.nonNull(targetObject.getDist())) {
527 updateStateFromUDPEvent("dist", targetObject);
532 private void updateStateFromUDPEvent(String zoneToUpdate, UdpMessage targetObject) {
534 String playInfoUpdated = "";
535 String statusUpdated = "";
536 String powerState = "";
537 String muteState = "";
538 String inputState = "";
540 float volumeDbState = -90f; // -90.0 dB
541 int presetNumber = 0;
543 String distInfoUpdated = "";
544 logger.trace("Handling UDP for {}", zoneToUpdate);
545 switch (zoneToUpdate) {
547 powerState = targetObject.getMain().getPower();
548 muteState = targetObject.getMain().getMute();
549 inputState = targetObject.getMain().getInput();
550 volumeState = targetObject.getMain().getVolume();
551 volumeDbState = targetObject.getMain().getVolumeDb();
552 statusUpdated = targetObject.getMain().getstatusUpdated();
555 powerState = targetObject.getZone2().getPower();
556 muteState = targetObject.getZone2().getMute();
557 inputState = targetObject.getZone2().getInput();
558 volumeState = targetObject.getZone2().getVolume();
559 volumeDbState = targetObject.getZone2().getVolumeDb();
560 statusUpdated = targetObject.getZone2().getstatusUpdated();
563 powerState = targetObject.getZone3().getPower();
564 muteState = targetObject.getZone3().getMute();
565 inputState = targetObject.getZone3().getInput();
566 volumeState = targetObject.getZone3().getVolume();
567 volumeDbState = targetObject.getZone3().getVolumeDb();
568 statusUpdated = targetObject.getZone3().getstatusUpdated();
571 powerState = targetObject.getZone4().getPower();
572 muteState = targetObject.getZone4().getMute();
573 inputState = targetObject.getZone4().getInput();
574 volumeState = targetObject.getZone4().getVolume();
575 volumeDbState = targetObject.getZone4().getVolumeDb();
576 statusUpdated = targetObject.getZone4().getstatusUpdated();
579 if (Objects.isNull(targetObject.getNetUSB().getPresetControl())) {
582 presetNumber = targetObject.getNetUSB().getPresetControl().getNum();
584 playInfoUpdated = targetObject.getNetUSB().getPlayInfoUpdated();
585 playTime = targetObject.getNetUSB().getPlayTime();
586 // totalTime is not in UDP event
589 distInfoUpdated = targetObject.getDist().getDistInfoUpdated();
593 if (!powerState.isEmpty()) {
594 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_POWER);
595 if ("on".equals(powerState)) {
596 updateState(channel, OnOffType.ON);
597 } else if ("standby".equals(powerState)) {
598 updateState(channel, OnOffType.OFF);
603 if (!muteState.isEmpty()) {
604 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_MUTE);
605 if ("true".equals(muteState)) {
606 updateState(channel, OnOffType.ON);
607 } else if ("false".equals(muteState)) {
608 updateState(channel, OnOffType.OFF);
612 if (!inputState.isEmpty()) {
613 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_INPUT);
614 updateState(channel, StringType.valueOf(inputState));
617 if (volumeState != 0) {
618 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUME);
619 updateState(channel, new PercentType((volumeState * 100) / maxVolumeState));
620 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUMEABS);
621 updateState(channel, new DecimalType(volumeState));
624 if (volumeDbState != -90f) {
625 channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUMEDB);
626 updateState(channel, new QuantityType<>(volumeDbState, Units.DECIBEL));
629 if (presetNumber != 0) {
630 logger.trace("Preset detected: {}", presetNumber);
631 updatePresets(presetNumber);
634 if ("true".equals(playInfoUpdated)) {
635 updateNetUSBPlayer();
638 if (!statusUpdated.isEmpty()) {
639 updateStatusZone(zoneToUpdate);
642 channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME);
643 updateState(channel, StringType.valueOf(String.valueOf(playTime)));
645 if ("true".equals(distInfoUpdated)) {
646 updateMCLinkStatus();
650 private void updateStatusZone(String zoneToUpdate) {
651 String localZone = "";
652 tmpString = getStatus(this.host, zoneToUpdate);
654 Status targetObject = gson.fromJson(tmpString, Status.class);
655 if (targetObject != null) {
656 String responseCode = targetObject.getResponseCode();
657 String powerState = targetObject.getPower();
658 String muteState = targetObject.getMute();
659 volumeState = targetObject.getVolume();
660 volumeDbState = targetObject.getVolumeDb();
661 maxVolumeState = targetObject.getMaxVolume();
662 inputState = targetObject.getInput();
663 soundProgramState = targetObject.getSoundProgram();
664 sleepState = targetObject.getSleep();
666 logger.trace("{} - Response: {}", zoneToUpdate, responseCode);
667 logger.trace("{} - Power: {}", zoneToUpdate, powerState);
668 logger.trace("{} - Mute: {}", zoneToUpdate, muteState);
669 logger.trace("{} - Volume: {}", zoneToUpdate, volumeState);
670 logger.trace("{} - Volume in dB: {}", zoneToUpdate, volumeDbState);
671 logger.trace("{} - Max Volume: {}", zoneToUpdate, maxVolumeState);
672 logger.trace("{} - Input: {}", zoneToUpdate, inputState);
673 logger.trace("{} - Soundprogram: {}", zoneToUpdate, soundProgramState);
674 logger.trace("{} - Sleep: {}", zoneToUpdate, sleepState);
676 switch (responseCode) {
678 for (Channel channel : getThing().getChannels()) {
679 ChannelUID channelUID = channel.getUID();
680 channelWithoutGroup = channelUID.getIdWithoutGroup();
681 localZone = channelUID.getGroupId();
682 if (localZone != null) {
683 if (isLinked(channelUID)) {
684 switch (channelWithoutGroup) {
686 if ("on".equals(powerState)) {
687 if (localZone.equals(zoneToUpdate)) {
688 updateState(channelUID, OnOffType.ON);
690 } else if ("standby".equals(powerState)) {
691 if (localZone.equals(zoneToUpdate)) {
692 updateState(channelUID, OnOffType.OFF);
697 if ("true".equals(muteState)) {
698 if (localZone.equals(zoneToUpdate)) {
699 updateState(channelUID, OnOffType.ON);
701 } else if ("false".equals(muteState)) {
702 if (localZone.equals(zoneToUpdate)) {
703 updateState(channelUID, OnOffType.OFF);
708 if (localZone.equals(zoneToUpdate)) {
709 updateState(channelUID,
710 new PercentType((volumeState * 100) / maxVolumeState));
713 case CHANNEL_VOLUMEABS:
714 if (localZone.equals(zoneToUpdate)) {
715 updateState(channelUID, new DecimalType(volumeState));
718 case CHANNEL_VOLUMEDB:
719 if (localZone.equals(zoneToUpdate)) {
720 updateState(channelUID, new QuantityType<>(volumeDbState, Units.DECIBEL));
724 if (localZone.equals(zoneToUpdate)) {
725 updateState(channelUID, StringType.valueOf(inputState));
728 case CHANNEL_SOUNDPROGRAM:
729 if (localZone.equals(zoneToUpdate)) {
730 updateState(channelUID, StringType.valueOf(soundProgramState));
734 if (localZone.equals(zoneToUpdate)) {
735 updateState(channelUID, new DecimalType(sleepState));
738 } // END switch (channelWithoutGroup)
744 logger.trace("Nothing to do! - {} ({})", thingLabel, zoneToUpdate);
750 private void updatePresets(int value) {
751 String inputText = "";
752 int presetCounter = 0;
753 int currentPreset = 0;
754 tmpString = getPresetInfo(this.host);
756 PresetInfo presetinfo = gson.fromJson(tmpString, PresetInfo.class);
757 if (presetinfo != null) {
758 String responseCode = presetinfo.getResponseCode();
759 if ("0".equals(responseCode)) {
760 List<StateOption> optionsPresets = new ArrayList<>();
761 inputText = getLastInput();
762 if (inputText != null) {
763 for (JsonElement pr : presetinfo.getPresetInfo()) {
764 presetCounter = presetCounter + 1;
765 JsonObject presetObject = pr.getAsJsonObject();
766 String text = presetObject.get("text").getAsString();
767 if (!"".equals(text)) {
768 optionsPresets.add(new StateOption(String.valueOf(presetCounter),
769 "#" + String.valueOf(presetCounter) + " " + text));
770 if (inputText.equals(text)) {
771 currentPreset = presetCounter;
777 currentPreset = value;
779 for (Channel channel : getThing().getChannels()) {
780 ChannelUID channelUID = channel.getUID();
781 channelWithoutGroup = channelUID.getIdWithoutGroup();
782 if (isLinked(channelUID)) {
783 switch (channelWithoutGroup) {
784 case CHANNEL_SELECTPRESET:
785 stateDescriptionProvider.setStateOptions(channelUID, optionsPresets);
786 updateState(channelUID, StringType.valueOf(String.valueOf(currentPreset)));
795 private void updateNetUSBPlayer() {
796 tmpString = getPlayInfo(this.host);
799 PlayInfo targetObject = gson.fromJson(tmpString, PlayInfo.class);
800 if (targetObject != null) {
801 String responseCode = targetObject.getResponseCode();
802 String playbackState = targetObject.getPlayback();
803 artistState = targetObject.getArtist();
804 trackState = targetObject.getTrack();
805 albumState = targetObject.getAlbum();
806 String albumArtUrlState = targetObject.getAlbumArtUrl();
807 repeatState = targetObject.getRepeat();
808 shuffleState = targetObject.getShuffle();
809 playTimeState = targetObject.getPlayTime();
810 totalTimeState = targetObject.getTotalTime();
812 if ("0".equals(responseCode)) {
813 ChannelUID testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYER);
814 switch (playbackState) {
816 updateState(testchannel, PlayPauseType.PLAY);
819 updateState(testchannel, PlayPauseType.PAUSE);
822 updateState(testchannel, PlayPauseType.PAUSE);
825 updateState(testchannel, RewindFastforwardType.REWIND);
828 updateState(testchannel, RewindFastforwardType.FASTFORWARD);
831 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST);
832 updateState(testchannel, StringType.valueOf(artistState));
833 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK);
834 updateState(testchannel, StringType.valueOf(trackState));
835 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM);
836 updateState(testchannel, StringType.valueOf(albumState));
837 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUMART);
838 if (!"".equals(albumArtUrlState)) {
839 albumArtUrlState = HTTP + this.host + albumArtUrlState;
841 updateState(testchannel, StringType.valueOf(albumArtUrlState));
842 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_REPEAT);
843 updateState(testchannel, StringType.valueOf(repeatState));
844 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_SHUFFLE);
845 updateState(testchannel, StringType.valueOf(shuffleState));
846 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME);
847 updateState(testchannel, StringType.valueOf(String.valueOf(playTimeState)));
848 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TOTALTIME);
849 updateState(testchannel, StringType.valueOf(String.valueOf(totalTimeState)));
854 private @Nullable String getLastInput() {
856 tmpString = getRecentInfo(this.host);
857 RecentInfo recentinfo = gson.fromJson(tmpString, RecentInfo.class);
858 if (recentinfo != null) {
859 String responseCode = recentinfo.getResponseCode();
860 if ("0".equals(responseCode)) {
861 for (JsonElement ri : recentinfo.getRecentInfo()) {
862 JsonObject recentObject = ri.getAsJsonObject();
863 text = recentObject.get("text").getAsString();
871 private String connectedServer() {
872 DistributionInfo distributioninfo = new DistributionInfo();
873 Bridge bridge = getBridge();
874 String remotehost = "";
876 String localHost = "";
877 if (bridge != null) {
878 for (Thing thing : bridge.getThings()) {
879 remotehost = thing.getConfiguration().get("host").toString();
880 tmpString = getDistributionInfo(remotehost);
881 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
882 if (distributioninfo != null) {
883 String localRole = distributioninfo.getRole();
884 if ("server".equals(localRole)) {
885 for (JsonElement ip : distributioninfo.getClientList()) {
886 JsonObject clientObject = ip.getAsJsonObject();
887 localHost = getThing().getConfiguration().get("host").toString();
888 if (localHost.equals(clientObject.get("ip_address").getAsString())) {
900 private void fillOptionsForMCLink() {
901 Bridge bridge = getBridge();
904 int zonesPerHost = 1;
906 tmpString = getDistributionInfo(this.host);
907 DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class);
908 if (targetObject != null) {
909 clients = targetObject.getClientList().size();
912 List<StateOption> options = new ArrayList<>();
913 // first add 3 options for MC Link
914 options.add(new StateOption("", "Standalone"));
915 options.add(new StateOption("server", "Server: " + clients + " clients"));
916 options.add(new StateOption("client", "Client"));
918 if (bridge != null) {
919 for (Thing thing : bridge.getThings()) {
920 label = thing.getLabel();
921 host = thing.getConfiguration().get("host").toString();
922 logger.trace("Thing found on Bridge: {} - {}", label, host);
923 zonesPerHost = getNumberOfZones(host);
924 for (int i = 1; i <= zonesPerHost; i++) {
927 options.add(new StateOption(host + "***main", label + " - main (" + host + ")"));
930 options.add(new StateOption(host + "***zone2", label + " - zone2 (" + host + ")"));
933 options.add(new StateOption(host + "***zone3", label + " - zone3 (" + host + ")"));
936 options.add(new StateOption(host + "***zone4", label + " - zone4 (" + host + ")"));
943 // for each zone of the device, set all the possible combinations
944 ChannelUID testchannel;
945 for (int i = 1; i <= zoneNum; i++) {
948 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
949 if (isLinked(testchannel)) {
950 stateDescriptionProvider.setStateOptions(testchannel, options);
954 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
955 if (isLinked(testchannel)) {
956 stateDescriptionProvider.setStateOptions(testchannel, options);
960 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
961 if (isLinked(testchannel)) {
962 stateDescriptionProvider.setStateOptions(testchannel, options);
966 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
967 if (isLinked(testchannel)) {
968 stateDescriptionProvider.setStateOptions(testchannel, options);
975 private String generateGroupId() {
976 return UUID.randomUUID().toString().replace("-", "").substring(0, 32);
979 private int getNumberOfZones(@Nullable String host) {
980 int numberOfZones = 0;
981 tmpString = getFeatures(host);
983 Features targetObject = gson.fromJson(tmpString, Features.class);
984 if (targetObject != null) {
985 responseCode = targetObject.getResponseCode();
986 if ("0".equals(responseCode)) {
987 numberOfZones = targetObject.getSystem().getZoneNum();
990 return numberOfZones;
993 public @Nullable String getDeviceId() {
994 tmpString = getDeviceInfo(this.host);
995 String localValueToCheck = "";
997 DeviceInfo targetObject = gson.fromJson(tmpString, DeviceInfo.class);
998 if (targetObject != null) {
999 localValueToCheck = targetObject.getDeviceId();
1001 return localValueToCheck;
1004 private void setVolumeLinkedDevice(int value, @Nullable String zone, String host) {
1005 logger.trace("setVolumeLinkedDevice: {}", host);
1006 int zoneNumLinkedDevice = getNumberOfZones(host);
1007 int maxVolumeLinkedDevice = 0;
1009 Status targetObject = new Status();
1011 for (int i = 1; i <= zoneNumLinkedDevice; i++) {
1014 tmpString = getStatus(host, "main");
1015 targetObject = gson.fromJson(tmpString, Status.class);
1016 if (targetObject != null) {
1017 responseCode = targetObject.getResponseCode();
1018 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1019 newVolume = maxVolumeLinkedDevice * value / 100;
1020 setVolume(newVolume, "main", host);
1024 tmpString = getStatus(host, "zone2");
1025 targetObject = gson.fromJson(tmpString, Status.class);
1026 if (targetObject != null) {
1027 responseCode = targetObject.getResponseCode();
1028 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1029 newVolume = maxVolumeLinkedDevice * value / 100;
1030 setVolume(newVolume, "zone2", host);
1034 tmpString = getStatus(host, "zone3");
1035 targetObject = gson.fromJson(tmpString, Status.class);
1036 if (targetObject != null) {
1037 responseCode = targetObject.getResponseCode();
1038 maxVolumeLinkedDevice = targetObject.getMaxVolume();
1039 newVolume = maxVolumeLinkedDevice * value / 100;
1040 setVolume(newVolume, "zone3", host);
1044 tmpString = getStatus(host, "zone4");
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, "zone4", host);
1057 private void setVolumeDbLinkedDevice(float value, @Nullable String zone, String host) {
1058 logger.trace("setVolumeDbLinkedDevice: {}", host);
1059 int zoneNumLinkedDevice = getNumberOfZones(host);
1060 for (int i = 1; i <= zoneNumLinkedDevice; i++) {
1063 setVolumeDb(value, "main", host);
1066 setVolumeDb(value, "zone2", host);
1069 setVolumeDb(value, "zone3", host);
1072 setVolumeDb(value, "zone4", host);
1078 public void updateMCLinkStatus() {
1079 tmpString = getDistributionInfo(this.host);
1081 DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class);
1082 if (targetObject != null) {
1083 String localRole = targetObject.getRole();
1084 groupId = targetObject.getGroupId();
1085 switch (localRole) {
1087 setMCLinkToStandalone();
1090 setMCLinkToServer();
1093 setMCLinkToClient();
1099 private void setMCLinkToStandalone() {
1100 ChannelUID testchannel;
1101 for (int i = 1; i <= zoneNum; i++) {
1104 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1105 updateState(testchannel, StringType.valueOf(""));
1108 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1109 updateState(testchannel, StringType.valueOf(""));
1112 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1113 updateState(testchannel, StringType.valueOf(""));
1116 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1117 updateState(testchannel, StringType.valueOf(""));
1123 private void setMCLinkToClient() {
1124 ChannelUID testchannel;
1125 for (int i = 1; i <= zoneNum; i++) {
1128 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1129 updateState(testchannel, StringType.valueOf("client"));
1132 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1133 updateState(testchannel, StringType.valueOf("client"));
1136 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1137 updateState(testchannel, StringType.valueOf("client"));
1140 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1141 updateState(testchannel, StringType.valueOf("client"));
1147 private void setMCLinkToServer() {
1148 ChannelUID testchannel;
1149 for (int i = 1; i <= zoneNum; i++) {
1152 testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1153 updateState(testchannel, StringType.valueOf("server"));
1156 testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1157 updateState(testchannel, StringType.valueOf("server"));
1160 testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1161 updateState(testchannel, StringType.valueOf("server"));
1164 testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1165 updateState(testchannel, StringType.valueOf("server"));
1171 private String makeRequest(@Nullable String topicAVR, String url) {
1172 String response = "";
1174 response = HttpUtil.executeUrl("GET", HTTP + url, LONG_CONNECTION_TIMEOUT_MILLISEC);
1175 logger.trace("{} - {}", topicAVR, response);
1177 } catch (IOException e) {
1178 logger.trace("IO Exception - {} - {}", topicAVR, e.getMessage());
1179 return "{\"response_code\":\"999\"}";
1182 // End Various functions
1186 // Start Zone Related
1188 private @Nullable String getStatus(@Nullable String host, String zone) {
1189 return makeRequest("Status", host + YAMAHA_EXTENDED_CONTROL + zone + "/getStatus");
1192 private @Nullable String setPower(String value, @Nullable String zone, @Nullable String host) {
1193 return makeRequest("Power", host + YAMAHA_EXTENDED_CONTROL + zone + "/setPower?power=" + value);
1196 private @Nullable String setMute(String value, @Nullable String zone, @Nullable String host) {
1197 return makeRequest("Mute", host + YAMAHA_EXTENDED_CONTROL + zone + "/setMute?enable=" + value);
1200 private @Nullable String setVolume(int value, @Nullable String zone, @Nullable String host) {
1201 return makeRequest("Volume", host + YAMAHA_EXTENDED_CONTROL + zone + "/setVolume?volume=" + value);
1205 * Sets the volume in decibels (dB).
1207 * @param value volume in dB (decibels)
1208 * @param zone name of zone
1209 * @param host hostname or ip address
1210 * @return HTTP request
1212 private @Nullable String setVolumeDb(float value, @Nullable String zone, @Nullable String host) {
1213 float volumeDbMin = Float.parseFloat(getThing().getConfiguration().get("volumeDbMin").toString());
1214 float volumeDbMax = Float.parseFloat(getThing().getConfiguration().get("volumeDbMax").toString());
1215 if (value < volumeDbMin) {
1216 value = volumeDbMin;
1218 if (value > volumeDbMax) {
1219 value = volumeDbMax;
1222 // Yamaha accepts only integer values with .0 or .5 at the end only (-20.5dB, -20.0dB) - at least on RX-S601D.
1223 // The order matters here. We want to cast to integer first and then scale by 10.
1224 // Effectively we're only allowing dB values with .0 at the end.
1225 logger.trace("setVolumeDb: {} dB", value);
1226 return makeRequest("Volume", host + YAMAHA_EXTENDED_CONTROL + zone + "/setActualVolume?mode=db&value=" + value);
1229 private @Nullable String setInput(String value, @Nullable String zone, @Nullable String host) {
1230 return makeRequest("setInput", host + YAMAHA_EXTENDED_CONTROL + zone + "/setInput?input=" + value);
1233 private @Nullable String setSoundProgram(String value, @Nullable String zone, @Nullable String host) {
1234 return makeRequest("setSoundProgram",
1235 host + YAMAHA_EXTENDED_CONTROL + zone + "/setSoundProgram?program=" + value);
1238 private @Nullable String setPreset(String value, @Nullable String zone, @Nullable String host) {
1239 return makeRequest("setPreset",
1240 host + YAMAHA_EXTENDED_CONTROL + "netusb/recallPreset?zone=" + zone + "&num=" + value);
1243 private @Nullable String setSleep(String value, @Nullable String zone, @Nullable String host) {
1244 return makeRequest("setSleep", host + YAMAHA_EXTENDED_CONTROL + zone + "/setSleep?sleep=" + value);
1247 private @Nullable String recallScene(String value, @Nullable String zone, @Nullable String host) {
1248 return makeRequest("recallScene", host + YAMAHA_EXTENDED_CONTROL + zone + "/recallScene?num=" + value);
1252 // Start Net Radio/USB Related
1254 private @Nullable String getPresetInfo(@Nullable String host) {
1255 return makeRequest("PresetInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPresetInfo");
1258 private @Nullable String getRecentInfo(@Nullable String host) {
1259 return makeRequest("RecentInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getRecentInfo");
1262 private @Nullable String getPlayInfo(@Nullable String host) {
1263 return makeRequest("PlayInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo");
1266 private @Nullable String setPlayback(String value, @Nullable String host) {
1267 return makeRequest("Playback", host + YAMAHA_EXTENDED_CONTROL + "netusb/setPlayback?playback=" + value);
1270 private @Nullable String setRepeat(String value, @Nullable String host) {
1271 return makeRequest("Repeat", host + YAMAHA_EXTENDED_CONTROL + "netusb/setRepeat?mode=" + value);
1274 private @Nullable String setShuffle(String value, @Nullable String host) {
1275 return makeRequest("Shuffle", host + YAMAHA_EXTENDED_CONTROL + "netusb/setShuffle?mode=" + value);
1278 // End Net Radio/USB Related
1280 // Start Music Cast API calls
1281 private @Nullable String getDistributionInfo(@Nullable String host) {
1282 return makeRequest("DistributionInfo", host + YAMAHA_EXTENDED_CONTROL + "dist/getDistributionInfo");
1285 private @Nullable String setClientServerInfo(@Nullable String host, String json, String type) {
1286 InputStream is = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
1288 url = "http://" + host + YAMAHA_EXTENDED_CONTROL + "dist/" + type;
1289 httpResponse = HttpUtil.executeUrl("POST", url, is, "", LONG_CONNECTION_TIMEOUT_MILLISEC);
1290 logger.trace("MC Link/Unlink Client {}", httpResponse);
1291 return httpResponse;
1292 } catch (IOException e) {
1293 logger.trace("IO Exception - {} - {}", type, e.getMessage());
1294 return "{\"response_code\":\"999\"}";
1298 private @Nullable String startDistribution(@Nullable String host) {
1299 Random ran = new Random();
1300 int nxt = ran.nextInt(200000);
1301 return makeRequest("StartDistribution", host + YAMAHA_EXTENDED_CONTROL + "dist/startDistribution?num=" + nxt);
1304 // End Music Cast API calls
1306 // Start General/System API calls
1308 private @Nullable String getFeatures(@Nullable String host) {
1309 return makeRequest("Features", host + YAMAHA_EXTENDED_CONTROL + "system/getFeatures");
1312 private @Nullable String getDeviceInfo(@Nullable String host) {
1313 return makeRequest("DeviceInfo", host + YAMAHA_EXTENDED_CONTROL + "system/getDeviceInfo");
1316 private void keepUdpEventsAlive(@Nullable String host) {
1317 Properties appProps = new Properties();
1318 appProps.setProperty("X-AppName", "MusicCast/1");
1319 appProps.setProperty("X-AppPort", "41100");
1321 httpResponse = HttpUtil.executeUrl("GET", HTTP + host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo",
1322 appProps, null, "", LONG_CONNECTION_TIMEOUT_MILLISEC);
1323 // logger.trace("{}", httpResponse);
1324 logger.trace("{} - {}", "UDP task", httpResponse);
1325 } catch (IOException e) {
1326 logger.trace("UDP refresh failed - {}", e.getMessage());
1329 // End General/System API calls