]> git.basschouten.com Git - openhab-addons.git/blob
2a0761585f59a2ac001abe53175f013a33ff3b0f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.yamahamusiccast.internal;
14
15 import static org.openhab.binding.yamahamusiccast.internal.YamahaMusiccastBindingConstants.*;
16
17 import java.io.ByteArrayInputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.nio.charset.StandardCharsets;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Objects;
24 import java.util.Properties;
25 import java.util.Random;
26 import java.util.UUID;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.yamahamusiccast.internal.dto.DeviceInfo;
33 import org.openhab.binding.yamahamusiccast.internal.dto.DistributionInfo;
34 import org.openhab.binding.yamahamusiccast.internal.dto.Features;
35 import org.openhab.binding.yamahamusiccast.internal.dto.PlayInfo;
36 import org.openhab.binding.yamahamusiccast.internal.dto.PresetInfo;
37 import org.openhab.binding.yamahamusiccast.internal.dto.RecentInfo;
38 import org.openhab.binding.yamahamusiccast.internal.dto.Response;
39 import org.openhab.binding.yamahamusiccast.internal.dto.Status;
40 import org.openhab.binding.yamahamusiccast.internal.dto.UdpMessage;
41 import org.openhab.core.io.net.http.HttpUtil;
42 import org.openhab.core.library.types.DecimalType;
43 import org.openhab.core.library.types.NextPreviousType;
44 import org.openhab.core.library.types.OnOffType;
45 import org.openhab.core.library.types.PercentType;
46 import org.openhab.core.library.types.PlayPauseType;
47 import org.openhab.core.library.types.RewindFastforwardType;
48 import org.openhab.core.library.types.StringType;
49 import org.openhab.core.thing.Bridge;
50 import org.openhab.core.thing.Channel;
51 import org.openhab.core.thing.ChannelUID;
52 import org.openhab.core.thing.Thing;
53 import org.openhab.core.thing.ThingStatus;
54 import org.openhab.core.thing.ThingStatusDetail;
55 import org.openhab.core.thing.binding.BaseThingHandler;
56 import org.openhab.core.thing.binding.builder.ChannelBuilder;
57 import org.openhab.core.thing.binding.builder.ThingBuilder;
58 import org.openhab.core.thing.type.ChannelTypeUID;
59 import org.openhab.core.types.Command;
60 import org.openhab.core.types.RefreshType;
61 import org.openhab.core.types.StateOption;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 import com.google.gson.Gson;
66 import com.google.gson.JsonElement;
67 import com.google.gson.JsonObject;
68
69 /**
70  * The {@link YamahaMusiccastHandler} is responsible for handling commands, which are
71  * sent to one of the channels.
72  *
73  * @author Lennert Coopman - Initial contribution
74  */
75 @NonNullByDefault
76 public class YamahaMusiccastHandler extends BaseThingHandler {
77     private Gson gson = new Gson();
78     private Logger logger = LoggerFactory.getLogger(YamahaMusiccastHandler.class);
79     private @Nullable ScheduledFuture<?> generalHousekeepingTask;
80     private @Nullable String httpResponse;
81     private @Nullable String tmpString = "";
82     private int volumePercent = 0;
83     private int volumeAbsValue = 0;
84     private @Nullable String responseCode = "";
85     private int volumeState = 0;
86     private int maxVolumeState = 0;
87     private @Nullable String inputState = "";
88     private @Nullable String soundProgramState = "";
89     private int sleepState = 0;
90     private @Nullable String artistState = "";
91     private @Nullable String trackState = "";
92     private @Nullable String albumState = "";
93     private @Nullable String repeatState = "";
94     private @Nullable String shuffleState = "";
95     private int playTimeState = 0;
96     private int totalTimeState = 0;
97     private @Nullable String zone = "main";
98     private String channelWithoutGroup = "";
99     private @Nullable String thingLabel = "";
100     private @Nullable String mclinkSetupServer = "";
101     private @Nullable String mclinkSetupZone = "";
102     private String url = "";
103     private String json = "";
104     private String action = "";
105     private int zoneNum = 0;
106     private @Nullable String groupId = "";
107     private @Nullable String host;
108     public @Nullable String deviceId = "";
109
110     private YamahaMusiccastStateDescriptionProvider stateDescriptionProvider;
111
112     public YamahaMusiccastHandler(Thing thing, YamahaMusiccastStateDescriptionProvider stateDescriptionProvider) {
113         super(thing);
114         this.stateDescriptionProvider = stateDescriptionProvider;
115     }
116
117     @Override
118     public void handleCommand(ChannelUID channelUID, Command command) {
119         String localValueToCheck = "";
120         String localRole = "";
121         boolean localSyncVolume;
122         String localDefaultAfterMCLink = "";
123         String localRoleSelectedThing = "";
124         if (command != RefreshType.REFRESH) {
125             logger.trace("Handling command {} for channel {}", command, channelUID);
126             channelWithoutGroup = channelUID.getIdWithoutGroup();
127             zone = channelUID.getGroupId();
128             DistributionInfo distributioninfo = new DistributionInfo();
129             Response response = new Response();
130             switch (channelWithoutGroup) {
131                 case CHANNEL_POWER:
132                     if (command == OnOffType.ON) {
133                         httpResponse = setPower("on", zone, this.host);
134                         response = gson.fromJson(httpResponse, Response.class);
135                         if (response != null) {
136                             localValueToCheck = response.getResponseCode();
137                             if (!"0".equals(localValueToCheck)) {
138                                 updateState(channelUID, OnOffType.OFF);
139                             }
140                         }
141                         // check on scheduler task for UDP events
142                         ScheduledFuture<?> localGeneralHousekeepingTask = generalHousekeepingTask;
143                         if (localGeneralHousekeepingTask == null) {
144                             logger.trace("YXC - No scheduler task found!");
145                             generalHousekeepingTask = scheduler.scheduleWithFixedDelay(this::generalHousekeeping, 5,
146                                     300, TimeUnit.SECONDS);
147
148                         } else {
149                             logger.trace("Scheduler task found!");
150                         }
151
152                     } else if (command == OnOffType.OFF) {
153                         httpResponse = setPower("standby", zone, this.host);
154                         response = gson.fromJson(httpResponse, Response.class);
155                         powerOffCleanup();
156                         if (response != null) {
157                             localValueToCheck = response.getResponseCode();
158                             if (!"0".equals(localValueToCheck)) {
159                                 updateState(channelUID, OnOffType.ON);
160                             }
161                         }
162                     }
163                     break;
164                 case CHANNEL_MUTE:
165                     if (command == OnOffType.ON) {
166                         httpResponse = setMute("true", zone, this.host);
167                         response = gson.fromJson(httpResponse, Response.class);
168                         if (response != null) {
169                             localValueToCheck = response.getResponseCode();
170                             if (!"0".equals(localValueToCheck)) {
171                                 updateState(channelUID, OnOffType.OFF);
172                             }
173                         }
174                     } else if (command == OnOffType.OFF) {
175                         httpResponse = setMute("false", zone, this.host);
176                         response = gson.fromJson(httpResponse, Response.class);
177                         if (response != null) {
178                             localValueToCheck = response.getResponseCode();
179                             if (!"0".equals(localValueToCheck)) {
180                                 updateState(channelUID, OnOffType.ON);
181                             }
182                         }
183                     }
184                     break;
185                 case CHANNEL_VOLUME:
186                     volumePercent = Integer.parseInt(command.toString().replace(".0", ""));
187                     volumeAbsValue = (maxVolumeState * volumePercent) / 100;
188                     setVolume(volumeAbsValue, zone, this.host);
189                     localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString());
190                     if (localSyncVolume == Boolean.TRUE) {
191                         tmpString = getDistributionInfo(this.host);
192                         distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
193                         if (distributioninfo != null) {
194                             localRole = distributioninfo.getRole();
195                             if ("server".equals(localRole)) {
196                                 for (JsonElement ip : distributioninfo.getClientList()) {
197                                     JsonObject clientObject = ip.getAsJsonObject();
198                                     setVolumeLinkedDevice(volumePercent, zone,
199                                             clientObject.get("ip_address").getAsString());
200                                 }
201                             }
202                         }
203                     } // END config.syncVolume
204                     break;
205                 case CHANNEL_VOLUMEABS:
206                     volumeAbsValue = Integer.parseInt(command.toString().replace(".0", ""));
207                     volumePercent = (volumeAbsValue / maxVolumeState) * 100;
208                     setVolume(volumeAbsValue, zone, this.host);
209                     localSyncVolume = Boolean.parseBoolean(getThing().getConfiguration().get("syncVolume").toString());
210                     if (localSyncVolume == Boolean.TRUE) {
211                         tmpString = getDistributionInfo(this.host);
212                         distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
213                         if (distributioninfo != null) {
214                             localRole = distributioninfo.getRole();
215                             if ("server".equals(localRole)) {
216                                 for (JsonElement ip : distributioninfo.getClientList()) {
217                                     JsonObject clientObject = ip.getAsJsonObject();
218                                     setVolumeLinkedDevice(volumePercent, zone,
219                                             clientObject.get("ip_address").getAsString());
220                                 }
221                             }
222                         }
223                     }
224                     break;
225                 case CHANNEL_INPUT:
226                     // if it is a client, disconnect it first.
227                     tmpString = getDistributionInfo(this.host);
228                     distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
229                     if (distributioninfo != null) {
230                         localRole = distributioninfo.getRole();
231                         if ("client".equals(localRole)) {
232                             json = "{\"group_id\":\"\"}";
233                             httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
234                         }
235                     }
236                     setInput(command.toString(), zone, this.host);
237                     break;
238                 case CHANNEL_SOUNDPROGRAM:
239                     setSoundProgram(command.toString(), zone, this.host);
240                     break;
241                 case CHANNEL_SELECTPRESET:
242                     setPreset(command.toString(), zone, this.host);
243                     break;
244                 case CHANNEL_PLAYER:
245                     if (command.equals(PlayPauseType.PLAY)) {
246                         setPlayback("play", this.host);
247                     } else if (command.equals(PlayPauseType.PAUSE)) {
248                         setPlayback("pause", this.host);
249                     } else if (command.equals(NextPreviousType.NEXT)) {
250                         setPlayback("next", this.host);
251                     } else if (command.equals(NextPreviousType.PREVIOUS)) {
252                         setPlayback("previous", this.host);
253                     } else if (command.equals(RewindFastforwardType.REWIND)) {
254                         setPlayback("fast_reverse_start", this.host);
255                     } else if (command.equals(RewindFastforwardType.FASTFORWARD)) {
256                         setPlayback("fast_forward_end", this.host);
257                     }
258                     break;
259                 case CHANNEL_SLEEP:
260                     setSleep(command.toString(), zone, this.host);
261                     break;
262                 case CHANNEL_MCLINKSTATUS:
263                     action = "";
264                     json = "";
265                     tmpString = getDistributionInfo(this.host);
266                     distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
267                     if (distributioninfo != null) {
268                         responseCode = distributioninfo.getResponseCode();
269                         localRole = distributioninfo.getRole();
270                         if (command.toString().equals("")) {
271                             action = "unlink";
272                             groupId = distributioninfo.getGroupId();
273                         } else if (command.toString().contains("***")) {
274                             action = "link";
275                             String[] parts = command.toString().split("\\*\\*\\*");
276                             if (parts.length > 1) {
277                                 mclinkSetupServer = parts[0];
278                                 mclinkSetupZone = parts[1];
279                                 tmpString = getDistributionInfo(mclinkSetupServer);
280                                 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
281                                 if (distributioninfo != null) {
282                                     responseCode = distributioninfo.getResponseCode();
283                                     localRoleSelectedThing = distributioninfo.getRole();
284                                     groupId = distributioninfo.getGroupId();
285                                     if (localRoleSelectedThing != null) {
286                                         if ("server".equals(localRoleSelectedThing)) {
287                                             groupId = distributioninfo.getGroupId();
288                                         } else if ("client".equals(localRoleSelectedThing)) {
289                                             groupId = "";
290                                         } else if ("none".equals(localRoleSelectedThing)) {
291                                             groupId = generateGroupId();
292                                         }
293                                     }
294                                 }
295                             }
296                         }
297
298                         if ("unlink".equals(action)) {
299                             json = "{\"group_id\":\"\"}";
300                             if (localRole != null) {
301                                 if ("server".equals(localRole)) {
302                                     httpResponse = setClientServerInfo(this.host, json, "setServerInfo");
303                                     // Set GroupId = "" for linked clients
304                                     if (distributioninfo != null) {
305                                         for (JsonElement ip : distributioninfo.getClientList()) {
306                                             JsonObject clientObject = ip.getAsJsonObject();
307                                             setClientServerInfo(clientObject.get("ip_address").getAsString(), json,
308                                                     "setClientInfo");
309                                         }
310                                     }
311                                 } else if ("client".equals(localRole)) {
312                                     mclinkSetupServer = connectedServer();
313                                     // Step 1. empty group on client
314                                     httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
315                                     // empty zone to respect defaults
316                                     if (!"".equals(mclinkSetupServer)) {
317                                         // Step 2. remove client from server
318                                         json = "{\"group_id\":\"" + groupId
319                                                 + "\", \"type\":\"remove\", \"client_list\":[\"" + this.host + "\"]}";
320                                         httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo");
321                                         // Step 3. reflect changes to master
322                                         httpResponse = startDistribution(mclinkSetupServer);
323                                         localDefaultAfterMCLink = getThing().getConfiguration()
324                                                 .get("defaultAfterMCLink").toString();
325                                         httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host);
326                                     } else if ("".equals(mclinkSetupServer)) {
327                                         // fallback in case client is removed from group by ending group on server side
328                                         localDefaultAfterMCLink = getThing().getConfiguration()
329                                                 .get("defaultAfterMCLink").toString();
330                                         httpResponse = setInput(localDefaultAfterMCLink.toString(), zone, this.host);
331                                     }
332                                 }
333                             }
334                         } else if ("link".equals(action)) {
335                             if (localRole != null) {
336                                 if ("none".equals(localRole)) {
337                                     json = "{\"group_id\":\"" + groupId + "\", \"zone\":\"" + mclinkSetupZone
338                                             + "\", \"type\":\"add\", \"client_list\":[\"" + this.host + "\"]}";
339                                     logger.trace("setServerInfo json: {}", json);
340                                     httpResponse = setClientServerInfo(mclinkSetupServer, json, "setServerInfo");
341                                     // All zones of Model are required for MC Link
342                                     tmpString = "";
343                                     for (int i = 1; i <= zoneNum; i++) {
344                                         switch (i) {
345                                             case 1:
346                                                 tmpString = "\"main\"";
347                                                 break;
348                                             case 2:
349                                                 tmpString = tmpString + ", \"zone2\"";
350                                                 break;
351                                             case 3:
352                                                 tmpString = tmpString + ", \"zone3\"";
353                                                 break;
354                                             case 4:
355                                                 tmpString = tmpString + ", \"zone4\"";
356                                                 break;
357                                         }
358                                     }
359                                     json = "{\"group_id\":\"" + groupId + "\", \"zone\":[" + tmpString + "]}";
360                                     logger.trace("setClientInfo json: {}", json);
361                                     httpResponse = setClientServerInfo(this.host, json, "setClientInfo");
362                                     httpResponse = startDistribution(mclinkSetupServer);
363                                 }
364                             }
365                         }
366                     }
367                     updateMCLinkStatus();
368                     break;
369                 case CHANNEL_RECALLSCENE:
370                     recallScene(command.toString(), zone, this.host);
371                     break;
372                 case CHANNEL_REPEAT:
373                     setRepeat(command.toString(), this.host);
374                     break;
375                 case CHANNEL_SHUFFLE:
376                     setShuffle(command.toString(), this.host);
377                     break;
378             } // END Switch Channel
379         }
380     }
381
382     @Override
383     public void initialize() {
384         String localHost = "";
385         thingLabel = thing.getLabel();
386         updateStatus(ThingStatus.UNKNOWN);
387         localHost = getThing().getConfiguration().get("host").toString();
388         this.host = localHost;
389         if (!"".equals(this.host)) {
390             zoneNum = getNumberOfZones(this.host);
391             logger.trace("Zones found: {} - {}", zoneNum, thingLabel);
392
393             if (zoneNum > 0) {
394                 refreshOnStartup();
395                 generalHousekeepingTask = scheduler.scheduleWithFixedDelay(this::generalHousekeeping, 5, 300,
396                         TimeUnit.SECONDS);
397                 updateStatus(ThingStatus.ONLINE);
398             } else {
399                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "No host found");
400             }
401         }
402     }
403
404     private void generalHousekeeping() {
405         thingLabel = thing.getLabel();
406         logger.trace("YXC - Start Keep Alive UDP events (5 minutes - {}) ", thingLabel);
407         keepUdpEventsAlive(this.host);
408         fillOptionsForMCLink();
409         updateMCLinkStatus();
410     }
411
412     private void refreshOnStartup() {
413         for (int i = 1; i <= zoneNum; i++) {
414             switch (i) {
415                 case 1:
416                     createChannels("main");
417                     updateStatusZone("main");
418                     break;
419                 case 2:
420                     createChannels("zone2");
421                     updateStatusZone("zone2");
422                     break;
423                 case 3:
424                     createChannels("zone3");
425                     updateStatusZone("zone3");
426                     break;
427                 case 4:
428                     createChannels("zone4");
429                     updateStatusZone("zone4");
430                     break;
431             }
432         }
433         updatePresets(0);
434         updateNetUSBPlayer();
435         fillOptionsForMCLink();
436         updateMCLinkStatus();
437     }
438
439     @Override
440     public void dispose() {
441         ScheduledFuture<?> localGeneralHousekeepingTask = generalHousekeepingTask;
442         if (localGeneralHousekeepingTask != null) {
443             localGeneralHousekeepingTask.cancel(true);
444         }
445     }
446
447     // Various functions
448
449     private void createChannels(String zone) {
450         createChannel(zone, CHANNEL_POWER, CHANNEL_TYPE_UID_POWER, "Switch");
451         createChannel(zone, CHANNEL_MUTE, CHANNEL_TYPE_UID_MUTE, "Switch");
452         createChannel(zone, CHANNEL_VOLUME, CHANNEL_TYPE_UID_VOLUME, "Dimmer");
453         createChannel(zone, CHANNEL_VOLUMEABS, CHANNEL_TYPE_UID_VOLUMEABS, "Number");
454         createChannel(zone, CHANNEL_INPUT, CHANNEL_TYPE_UID_INPUT, "String");
455         createChannel(zone, CHANNEL_SOUNDPROGRAM, CHANNEL_TYPE_UID_SOUNDPROGRAM, "String");
456         createChannel(zone, CHANNEL_SLEEP, CHANNEL_TYPE_UID_SLEEP, "Number");
457         createChannel(zone, CHANNEL_SELECTPRESET, CHANNEL_TYPE_UID_SELECTPRESET, "String");
458         createChannel(zone, CHANNEL_RECALLSCENE, CHANNEL_TYPE_UID_RECALLSCENE, "Number");
459         createChannel(zone, CHANNEL_MCLINKSTATUS, CHANNEL_TYPE_UID_MCLINKSTATUS, "String");
460     }
461
462     private void createChannel(String zone, String channel, ChannelTypeUID channelTypeUID, String itemType) {
463         ChannelUID channelToCheck = new ChannelUID(thing.getUID(), zone, channel);
464         if (thing.getChannel(channelToCheck) == null) {
465             ThingBuilder thingBuilder = editThing();
466             Channel testchannel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), zone, channel), itemType)
467                     .withType(channelTypeUID).build();
468             thingBuilder.withChannel(testchannel);
469             updateThing(thingBuilder.build());
470         }
471     }
472
473     private void powerOffCleanup() {
474         ChannelUID channel;
475         channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST);
476         updateState(channel, StringType.valueOf("-"));
477         channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK);
478         updateState(channel, StringType.valueOf("-"));
479         channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM);
480         updateState(channel, StringType.valueOf("-"));
481     }
482
483     public void processUDPEvent(String json, String trackingID) {
484         logger.trace("UDP package: {} (Tracking: {})", json, trackingID);
485         @Nullable
486         UdpMessage targetObject = gson.fromJson(json, UdpMessage.class);
487         if (targetObject != null) {
488             if (Objects.nonNull(targetObject.getMain())) {
489                 updateStateFromUDPEvent("main", targetObject);
490             }
491             if (Objects.nonNull(targetObject.getZone2())) {
492                 updateStateFromUDPEvent("zone2", targetObject);
493             }
494             if (Objects.nonNull(targetObject.getZone3())) {
495                 updateStateFromUDPEvent("zone3", targetObject);
496             }
497             if (Objects.nonNull(targetObject.getZone4())) {
498                 updateStateFromUDPEvent("zone4", targetObject);
499             }
500             if (Objects.nonNull(targetObject.getNetUSB())) {
501                 updateStateFromUDPEvent("netusb", targetObject);
502             }
503             if (Objects.nonNull(targetObject.getDist())) {
504                 updateStateFromUDPEvent("dist", targetObject);
505             }
506         }
507     }
508
509     private void updateStateFromUDPEvent(String zoneToUpdate, UdpMessage targetObject) {
510         ChannelUID channel;
511         String playInfoUpdated = "";
512         String statusUpdated = "";
513         String powerState = "";
514         String muteState = "";
515         String inputState = "";
516         int volumeState = 0;
517         int presetNumber = 0;
518         int playTime = 0;
519         String distInfoUpdated = "";
520         logger.trace("Handling UDP for {}", zoneToUpdate);
521         switch (zoneToUpdate) {
522             case "main":
523                 powerState = targetObject.getMain().getPower();
524                 muteState = targetObject.getMain().getMute();
525                 inputState = targetObject.getMain().getInput();
526                 volumeState = targetObject.getMain().getVolume();
527                 statusUpdated = targetObject.getMain().getstatusUpdated();
528                 break;
529             case "zone2":
530                 powerState = targetObject.getZone2().getPower();
531                 muteState = targetObject.getZone2().getMute();
532                 inputState = targetObject.getZone2().getInput();
533                 volumeState = targetObject.getZone2().getVolume();
534                 statusUpdated = targetObject.getZone2().getstatusUpdated();
535                 break;
536             case "zone3":
537                 powerState = targetObject.getZone3().getPower();
538                 muteState = targetObject.getZone3().getMute();
539                 inputState = targetObject.getZone3().getInput();
540                 volumeState = targetObject.getZone3().getVolume();
541                 statusUpdated = targetObject.getZone3().getstatusUpdated();
542                 break;
543             case "zone4":
544                 powerState = targetObject.getZone4().getPower();
545                 muteState = targetObject.getZone4().getMute();
546                 inputState = targetObject.getZone4().getInput();
547                 volumeState = targetObject.getZone4().getVolume();
548                 statusUpdated = targetObject.getZone4().getstatusUpdated();
549                 break;
550             case "netusb":
551                 if (Objects.isNull(targetObject.getNetUSB().getPresetControl())) {
552                     presetNumber = 0;
553                 } else {
554                     presetNumber = targetObject.getNetUSB().getPresetControl().getNum();
555                 }
556                 playInfoUpdated = targetObject.getNetUSB().getPlayInfoUpdated();
557                 playTime = targetObject.getNetUSB().getPlayTime();
558                 // totalTime is not in UDP event
559                 break;
560             case "dist":
561                 distInfoUpdated = targetObject.getDist().getDistInfoUpdated();
562                 break;
563         }
564
565         if (!powerState.isEmpty()) {
566             channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_POWER);
567             if ("on".equals(powerState)) {
568                 updateState(channel, OnOffType.ON);
569             } else if ("standby".equals(powerState)) {
570                 updateState(channel, OnOffType.OFF);
571                 powerOffCleanup();
572             }
573         }
574
575         if (!muteState.isEmpty()) {
576             channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_MUTE);
577             if ("true".equals(muteState)) {
578                 updateState(channel, OnOffType.ON);
579             } else if ("false".equals(muteState)) {
580                 updateState(channel, OnOffType.OFF);
581             }
582         }
583
584         if (!inputState.isEmpty()) {
585             channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_INPUT);
586             updateState(channel, StringType.valueOf(inputState));
587         }
588
589         if (volumeState != 0) {
590             channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUME);
591             updateState(channel, new PercentType((volumeState * 100) / maxVolumeState));
592             channel = new ChannelUID(getThing().getUID(), zoneToUpdate, CHANNEL_VOLUMEABS);
593             updateState(channel, new DecimalType(volumeState));
594         }
595
596         if (presetNumber != 0) {
597             logger.trace("Preset detected: {}", presetNumber);
598             updatePresets(presetNumber);
599         }
600
601         if ("true".equals(playInfoUpdated)) {
602             updateNetUSBPlayer();
603         }
604
605         if (!statusUpdated.isEmpty()) {
606             updateStatusZone(zoneToUpdate);
607         }
608         if (playTime != 0) {
609             channel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME);
610             updateState(channel, StringType.valueOf(String.valueOf(playTime)));
611         }
612         if ("true".equals(distInfoUpdated)) {
613             updateMCLinkStatus();
614         }
615     }
616
617     private void updateStatusZone(String zoneToUpdate) {
618         String localZone = "";
619         tmpString = getStatus(this.host, zoneToUpdate);
620         @Nullable
621         Status targetObject = gson.fromJson(tmpString, Status.class);
622         if (targetObject != null) {
623             String responseCode = targetObject.getResponseCode();
624             String powerState = targetObject.getPower();
625             String muteState = targetObject.getMute();
626             volumeState = targetObject.getVolume();
627             maxVolumeState = targetObject.getMaxVolume();
628             inputState = targetObject.getInput();
629             soundProgramState = targetObject.getSoundProgram();
630             sleepState = targetObject.getSleep();
631
632             logger.trace("{} - Response: {}", zoneToUpdate, responseCode);
633             logger.trace("{} - Power: {}", zoneToUpdate, powerState);
634             logger.trace("{} - Mute: {}", zoneToUpdate, muteState);
635             logger.trace("{} - Volume: {}", zoneToUpdate, volumeState);
636             logger.trace("{} - Max Volume: {}", zoneToUpdate, maxVolumeState);
637             logger.trace("{} - Input: {}", zoneToUpdate, inputState);
638             logger.trace("{} - Soundprogram: {}", zoneToUpdate, soundProgramState);
639             logger.trace("{} - Sleep: {}", zoneToUpdate, sleepState);
640
641             switch (responseCode) {
642                 case "0":
643                     for (Channel channel : getThing().getChannels()) {
644                         ChannelUID channelUID = channel.getUID();
645                         channelWithoutGroup = channelUID.getIdWithoutGroup();
646                         localZone = channelUID.getGroupId();
647                         if (localZone != null) {
648                             if (isLinked(channelUID)) {
649                                 switch (channelWithoutGroup) {
650                                     case CHANNEL_POWER:
651                                         if ("on".equals(powerState)) {
652                                             if (localZone.equals(zoneToUpdate)) {
653                                                 updateState(channelUID, OnOffType.ON);
654                                             }
655                                         } else if ("standby".equals(powerState)) {
656                                             if (localZone.equals(zoneToUpdate)) {
657                                                 updateState(channelUID, OnOffType.OFF);
658                                             }
659                                         }
660                                         break;
661                                     case CHANNEL_MUTE:
662                                         if ("true".equals(muteState)) {
663                                             if (localZone.equals(zoneToUpdate)) {
664                                                 updateState(channelUID, OnOffType.ON);
665                                             }
666                                         } else if ("false".equals(muteState)) {
667                                             if (localZone.equals(zoneToUpdate)) {
668                                                 updateState(channelUID, OnOffType.OFF);
669                                             }
670                                         }
671                                         break;
672                                     case CHANNEL_VOLUME:
673                                         if (localZone.equals(zoneToUpdate)) {
674                                             updateState(channelUID,
675                                                     new PercentType((volumeState * 100) / maxVolumeState));
676                                         }
677                                         break;
678                                     case CHANNEL_VOLUMEABS:
679                                         if (localZone.equals(zoneToUpdate)) {
680                                             updateState(channelUID, new DecimalType(volumeState));
681                                         }
682                                         break;
683                                     case CHANNEL_INPUT:
684                                         if (localZone.equals(zoneToUpdate)) {
685                                             updateState(channelUID, StringType.valueOf(inputState));
686                                         }
687                                         break;
688                                     case CHANNEL_SOUNDPROGRAM:
689                                         if (localZone.equals(zoneToUpdate)) {
690                                             updateState(channelUID, StringType.valueOf(soundProgramState));
691                                         }
692                                         break;
693                                     case CHANNEL_SLEEP:
694                                         if (localZone.equals(zoneToUpdate)) {
695                                             updateState(channelUID, new DecimalType(sleepState));
696                                         }
697                                         break;
698                                 } // END switch (channelWithoutGroup)
699                             } // END IsLinked
700                         }
701                     }
702                     break;
703                 case "999":
704                     logger.trace("Nothing to do! - {} ({})", thingLabel, zoneToUpdate);
705                     break;
706             }
707         }
708     }
709
710     private void updatePresets(int value) {
711         String inputText = "";
712         int presetCounter = 0;
713         int currentPreset = 0;
714         tmpString = getPresetInfo(this.host);
715
716         PresetInfo presetinfo = gson.fromJson(tmpString, PresetInfo.class);
717         if (presetinfo != null) {
718             String responseCode = presetinfo.getResponseCode();
719             if ("0".equals(responseCode)) {
720                 List<StateOption> optionsPresets = new ArrayList<>();
721                 inputText = getLastInput();
722                 if (inputText != null) {
723                     for (JsonElement pr : presetinfo.getPresetInfo()) {
724                         presetCounter = presetCounter + 1;
725                         JsonObject presetObject = pr.getAsJsonObject();
726                         String text = presetObject.get("text").getAsString();
727                         if (!"".equals(text)) {
728                             optionsPresets.add(new StateOption(String.valueOf(presetCounter),
729                                     "#" + String.valueOf(presetCounter) + " " + text));
730                             if (inputText.equals(text)) {
731                                 currentPreset = presetCounter;
732                             }
733                         }
734                     }
735                 }
736                 if (value != 0) {
737                     currentPreset = value;
738                 }
739                 for (Channel channel : getThing().getChannels()) {
740                     ChannelUID channelUID = channel.getUID();
741                     channelWithoutGroup = channelUID.getIdWithoutGroup();
742                     if (isLinked(channelUID)) {
743                         switch (channelWithoutGroup) {
744                             case CHANNEL_SELECTPRESET:
745                                 stateDescriptionProvider.setStateOptions(channelUID, optionsPresets);
746                                 updateState(channelUID, StringType.valueOf(String.valueOf(currentPreset)));
747                                 break;
748                         }
749                     }
750                 }
751             }
752         }
753     }
754
755     private void updateNetUSBPlayer() {
756         tmpString = getPlayInfo(this.host);
757
758         @Nullable
759         PlayInfo targetObject = gson.fromJson(tmpString, PlayInfo.class);
760         if (targetObject != null) {
761             String responseCode = targetObject.getResponseCode();
762             String playbackState = targetObject.getPlayback();
763             artistState = targetObject.getArtist();
764             trackState = targetObject.getTrack();
765             albumState = targetObject.getAlbum();
766             String albumArtUrlState = targetObject.getAlbumArtUrl();
767             repeatState = targetObject.getRepeat();
768             shuffleState = targetObject.getShuffle();
769             playTimeState = targetObject.getPlayTime();
770             totalTimeState = targetObject.getTotalTime();
771
772             if ("0".equals(responseCode)) {
773                 ChannelUID testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYER);
774                 switch (playbackState) {
775                     case "play":
776                         updateState(testchannel, PlayPauseType.PLAY);
777                         break;
778                     case "stop":
779                         updateState(testchannel, PlayPauseType.PAUSE);
780                         break;
781                     case "pause":
782                         updateState(testchannel, PlayPauseType.PAUSE);
783                         break;
784                     case "fast_reverse":
785                         updateState(testchannel, RewindFastforwardType.REWIND);
786                         break;
787                     case "fast_forward":
788                         updateState(testchannel, RewindFastforwardType.FASTFORWARD);
789                         break;
790                 }
791                 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ARTIST);
792                 updateState(testchannel, StringType.valueOf(artistState));
793                 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TRACK);
794                 updateState(testchannel, StringType.valueOf(trackState));
795                 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUM);
796                 updateState(testchannel, StringType.valueOf(albumState));
797                 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_ALBUMART);
798                 if (!"".equals(albumArtUrlState)) {
799                     albumArtUrlState = HTTP + this.host + albumArtUrlState;
800                 }
801                 updateState(testchannel, StringType.valueOf(albumArtUrlState));
802                 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_REPEAT);
803                 updateState(testchannel, StringType.valueOf(repeatState));
804                 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_SHUFFLE);
805                 updateState(testchannel, StringType.valueOf(shuffleState));
806                 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_PLAYTIME);
807                 updateState(testchannel, StringType.valueOf(String.valueOf(playTimeState)));
808                 testchannel = new ChannelUID(getThing().getUID(), "playerControls", CHANNEL_TOTALTIME);
809                 updateState(testchannel, StringType.valueOf(String.valueOf(totalTimeState)));
810             }
811         }
812     }
813
814     private @Nullable String getLastInput() {
815         String text = "";
816         tmpString = getRecentInfo(this.host);
817         RecentInfo recentinfo = gson.fromJson(tmpString, RecentInfo.class);
818         if (recentinfo != null) {
819             String responseCode = recentinfo.getResponseCode();
820             if ("0".equals(responseCode)) {
821                 for (JsonElement ri : recentinfo.getRecentInfo()) {
822                     JsonObject recentObject = ri.getAsJsonObject();
823                     text = recentObject.get("text").getAsString();
824                     break;
825                 }
826             }
827         }
828         return text;
829     }
830
831     private String connectedServer() {
832         DistributionInfo distributioninfo = new DistributionInfo();
833         Bridge bridge = getBridge();
834         String remotehost = "";
835         String result = "";
836         String localHost = "";
837         if (bridge != null) {
838             for (Thing thing : bridge.getThings()) {
839                 remotehost = thing.getConfiguration().get("host").toString();
840                 tmpString = getDistributionInfo(remotehost);
841                 distributioninfo = gson.fromJson(tmpString, DistributionInfo.class);
842                 if (distributioninfo != null) {
843                     String localRole = distributioninfo.getRole();
844                     if ("server".equals(localRole)) {
845                         for (JsonElement ip : distributioninfo.getClientList()) {
846                             JsonObject clientObject = ip.getAsJsonObject();
847                             localHost = getThing().getConfiguration().get("host").toString();
848                             if (localHost.equals(clientObject.get("ip_address").getAsString())) {
849                                 result = remotehost;
850                                 break;
851                             }
852                         }
853                     }
854                 }
855             }
856         }
857         return result;
858     }
859
860     private void fillOptionsForMCLink() {
861         Bridge bridge = getBridge();
862         String host = "";
863         String label = "";
864         int zonesPerHost = 1;
865         int clients = 0;
866         tmpString = getDistributionInfo(this.host);
867         DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class);
868         if (targetObject != null) {
869             clients = targetObject.getClientList().size();
870         }
871
872         List<StateOption> options = new ArrayList<>();
873         // first add 3 options for MC Link
874         options.add(new StateOption("", "Standalone"));
875         options.add(new StateOption("server", "Server: " + clients + " clients"));
876         options.add(new StateOption("client", "Client"));
877
878         if (bridge != null) {
879             for (Thing thing : bridge.getThings()) {
880                 label = thing.getLabel();
881                 host = thing.getConfiguration().get("host").toString();
882                 logger.trace("Thing found on Bridge: {} - {}", label, host);
883                 zonesPerHost = getNumberOfZones(host);
884                 for (int i = 1; i <= zonesPerHost; i++) {
885                     switch (i) {
886                         case 1:
887                             options.add(new StateOption(host + "***main", label + " - main (" + host + ")"));
888                             break;
889                         case 2:
890                             options.add(new StateOption(host + "***zone2", label + " - zone2 (" + host + ")"));
891                             break;
892                         case 3:
893                             options.add(new StateOption(host + "***zone3", label + " - zone3 (" + host + ")"));
894                             break;
895                         case 4:
896                             options.add(new StateOption(host + "***zone4", label + " - zone4 (" + host + ")"));
897                             break;
898                     }
899                 }
900
901             }
902         }
903         // for each zone of the device, set all the possible combinations
904         ChannelUID testchannel;
905         for (int i = 1; i <= zoneNum; i++) {
906             switch (i) {
907                 case 1:
908                     testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
909                     if (isLinked(testchannel)) {
910                         stateDescriptionProvider.setStateOptions(testchannel, options);
911                     }
912                     break;
913                 case 2:
914                     testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
915                     if (isLinked(testchannel)) {
916                         stateDescriptionProvider.setStateOptions(testchannel, options);
917                     }
918                     break;
919                 case 3:
920                     testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
921                     if (isLinked(testchannel)) {
922                         stateDescriptionProvider.setStateOptions(testchannel, options);
923                     }
924                     break;
925                 case 4:
926                     testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
927                     if (isLinked(testchannel)) {
928                         stateDescriptionProvider.setStateOptions(testchannel, options);
929                     }
930                     break;
931             }
932         }
933     }
934
935     private String generateGroupId() {
936         return UUID.randomUUID().toString().replace("-", "").substring(0, 32);
937     }
938
939     private int getNumberOfZones(@Nullable String host) {
940         int numberOfZones = 0;
941         tmpString = getFeatures(host);
942         @Nullable
943         Features targetObject = gson.fromJson(tmpString, Features.class);
944         if (targetObject != null) {
945             responseCode = targetObject.getResponseCode();
946             if ("0".equals(responseCode)) {
947                 numberOfZones = targetObject.getSystem().getZoneNum();
948             }
949         }
950         return numberOfZones;
951     }
952
953     public @Nullable String getDeviceId() {
954         tmpString = getDeviceInfo(this.host);
955         String localValueToCheck = "";
956         @Nullable
957         DeviceInfo targetObject = gson.fromJson(tmpString, DeviceInfo.class);
958         if (targetObject != null) {
959             localValueToCheck = targetObject.getDeviceId();
960         }
961         return localValueToCheck;
962     }
963
964     private void setVolumeLinkedDevice(int value, @Nullable String zone, String host) {
965         logger.trace("setVolumeLinkedDevice: {}", host);
966         int zoneNumLinkedDevice = getNumberOfZones(host);
967         int maxVolumeLinkedDevice = 0;
968         @Nullable
969         Status targetObject = new Status();
970         int newVolume = 0;
971         for (int i = 1; i <= zoneNumLinkedDevice; i++) {
972             switch (i) {
973                 case 1:
974                     tmpString = getStatus(host, "main");
975                     targetObject = gson.fromJson(tmpString, Status.class);
976                     if (targetObject != null) {
977                         responseCode = targetObject.getResponseCode();
978                         maxVolumeLinkedDevice = targetObject.getMaxVolume();
979                         newVolume = maxVolumeLinkedDevice * value / 100;
980                         setVolume(newVolume, "main", host);
981                     }
982                     break;
983                 case 2:
984                     tmpString = getStatus(host, "zone2");
985                     targetObject = gson.fromJson(tmpString, Status.class);
986                     if (targetObject != null) {
987                         responseCode = targetObject.getResponseCode();
988                         maxVolumeLinkedDevice = targetObject.getMaxVolume();
989                         newVolume = maxVolumeLinkedDevice * value / 100;
990                         setVolume(newVolume, "zone2", host);
991                     }
992                     break;
993                 case 3:
994                     tmpString = getStatus(host, "zone3");
995                     targetObject = gson.fromJson(tmpString, Status.class);
996                     if (targetObject != null) {
997                         responseCode = targetObject.getResponseCode();
998                         maxVolumeLinkedDevice = targetObject.getMaxVolume();
999                         newVolume = maxVolumeLinkedDevice * value / 100;
1000                         setVolume(newVolume, "zone3", host);
1001                     }
1002                     break;
1003                 case 4:
1004                     tmpString = getStatus(host, "zone4");
1005                     targetObject = gson.fromJson(tmpString, Status.class);
1006                     if (targetObject != null) {
1007                         responseCode = targetObject.getResponseCode();
1008                         maxVolumeLinkedDevice = targetObject.getMaxVolume();
1009                         newVolume = maxVolumeLinkedDevice * value / 100;
1010                         setVolume(newVolume, "zone4", host);
1011                     }
1012                     break;
1013             }
1014         }
1015     }
1016
1017     public void updateMCLinkStatus() {
1018         tmpString = getDistributionInfo(this.host);
1019         @Nullable
1020         DistributionInfo targetObject = gson.fromJson(tmpString, DistributionInfo.class);
1021         if (targetObject != null) {
1022             String localRole = targetObject.getRole();
1023             groupId = targetObject.getGroupId();
1024             switch (localRole) {
1025                 case "none":
1026                     setMCLinkToStandalone();
1027                     break;
1028                 case "server":
1029                     setMCLinkToServer();
1030                     break;
1031                 case "client":
1032                     setMCLinkToClient();
1033                     break;
1034             }
1035         }
1036     }
1037
1038     private void setMCLinkToStandalone() {
1039         ChannelUID testchannel;
1040         for (int i = 1; i <= zoneNum; i++) {
1041             switch (i) {
1042                 case 1:
1043                     testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1044                     updateState(testchannel, StringType.valueOf(""));
1045                     break;
1046                 case 2:
1047                     testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1048                     updateState(testchannel, StringType.valueOf(""));
1049                     break;
1050                 case 3:
1051                     testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1052                     updateState(testchannel, StringType.valueOf(""));
1053                     break;
1054                 case 4:
1055                     testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1056                     updateState(testchannel, StringType.valueOf(""));
1057                     break;
1058             }
1059         }
1060     }
1061
1062     private void setMCLinkToClient() {
1063         ChannelUID testchannel;
1064         for (int i = 1; i <= zoneNum; i++) {
1065             switch (i) {
1066                 case 1:
1067                     testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1068                     updateState(testchannel, StringType.valueOf("client"));
1069                     break;
1070                 case 2:
1071                     testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1072                     updateState(testchannel, StringType.valueOf("client"));
1073                     break;
1074                 case 3:
1075                     testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1076                     updateState(testchannel, StringType.valueOf("client"));
1077                     break;
1078                 case 4:
1079                     testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1080                     updateState(testchannel, StringType.valueOf("client"));
1081                     break;
1082             }
1083         }
1084     }
1085
1086     private void setMCLinkToServer() {
1087         ChannelUID testchannel;
1088         for (int i = 1; i <= zoneNum; i++) {
1089             switch (i) {
1090                 case 1:
1091                     testchannel = new ChannelUID(getThing().getUID(), "main", CHANNEL_MCLINKSTATUS);
1092                     updateState(testchannel, StringType.valueOf("server"));
1093                     break;
1094                 case 2:
1095                     testchannel = new ChannelUID(getThing().getUID(), "zone2", CHANNEL_MCLINKSTATUS);
1096                     updateState(testchannel, StringType.valueOf("server"));
1097                     break;
1098                 case 3:
1099                     testchannel = new ChannelUID(getThing().getUID(), "zone3", CHANNEL_MCLINKSTATUS);
1100                     updateState(testchannel, StringType.valueOf("server"));
1101                     break;
1102                 case 4:
1103                     testchannel = new ChannelUID(getThing().getUID(), "zone4", CHANNEL_MCLINKSTATUS);
1104                     updateState(testchannel, StringType.valueOf("server"));
1105                     break;
1106             }
1107         }
1108     }
1109
1110     private String makeRequest(@Nullable String topicAVR, String url) {
1111         String response = "";
1112         try {
1113             response = HttpUtil.executeUrl("GET", HTTP + url, LONG_CONNECTION_TIMEOUT_MILLISEC);
1114             logger.trace("{} - {}", topicAVR, response);
1115             return response;
1116         } catch (IOException e) {
1117             logger.trace("IO Exception - {} - {}", topicAVR, e.getMessage());
1118             return "{\"response_code\":\"999\"}";
1119         }
1120     }
1121     // End Various functions
1122
1123     // API calls to AVR
1124
1125     // Start Zone Related
1126
1127     private @Nullable String getStatus(@Nullable String host, String zone) {
1128         return makeRequest("Status", host + YAMAHA_EXTENDED_CONTROL + zone + "/getStatus");
1129     }
1130
1131     private @Nullable String setPower(String value, @Nullable String zone, @Nullable String host) {
1132         return makeRequest("Power", host + YAMAHA_EXTENDED_CONTROL + zone + "/setPower?power=" + value);
1133     }
1134
1135     private @Nullable String setMute(String value, @Nullable String zone, @Nullable String host) {
1136         return makeRequest("Mute", host + YAMAHA_EXTENDED_CONTROL + zone + "/setMute?enable=" + value);
1137     }
1138
1139     private @Nullable String setVolume(int value, @Nullable String zone, @Nullable String host) {
1140         return makeRequest("Volume", host + YAMAHA_EXTENDED_CONTROL + zone + "/setVolume?volume=" + value);
1141     }
1142
1143     private @Nullable String setInput(String value, @Nullable String zone, @Nullable String host) {
1144         return makeRequest("setInput", host + YAMAHA_EXTENDED_CONTROL + zone + "/setInput?input=" + value);
1145     }
1146
1147     private @Nullable String setSoundProgram(String value, @Nullable String zone, @Nullable String host) {
1148         return makeRequest("setSoundProgram",
1149                 host + YAMAHA_EXTENDED_CONTROL + zone + "/setSoundProgram?program=" + value);
1150     }
1151
1152     private @Nullable String setPreset(String value, @Nullable String zone, @Nullable String host) {
1153         return makeRequest("setPreset",
1154                 host + YAMAHA_EXTENDED_CONTROL + "netusb/recallPreset?zone=" + zone + "&num=" + value);
1155     }
1156
1157     private @Nullable String setSleep(String value, @Nullable String zone, @Nullable String host) {
1158         return makeRequest("setSleep", host + YAMAHA_EXTENDED_CONTROL + zone + "/setSleep?sleep=" + value);
1159     }
1160
1161     private @Nullable String recallScene(String value, @Nullable String zone, @Nullable String host) {
1162         return makeRequest("recallScene", host + YAMAHA_EXTENDED_CONTROL + zone + "/recallScene?num=" + value);
1163     }
1164     // End Zone Related
1165
1166     // Start Net Radio/USB Related
1167
1168     private @Nullable String getPresetInfo(@Nullable String host) {
1169         return makeRequest("PresetInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPresetInfo");
1170     }
1171
1172     private @Nullable String getRecentInfo(@Nullable String host) {
1173         return makeRequest("RecentInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getRecentInfo");
1174     }
1175
1176     private @Nullable String getPlayInfo(@Nullable String host) {
1177         return makeRequest("PlayInfo", host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo");
1178     }
1179
1180     private @Nullable String setPlayback(String value, @Nullable String host) {
1181         return makeRequest("Playback", host + YAMAHA_EXTENDED_CONTROL + "netusb/setPlayback?playback=" + value);
1182     }
1183
1184     private @Nullable String setRepeat(String value, @Nullable String host) {
1185         return makeRequest("Repeat", host + YAMAHA_EXTENDED_CONTROL + "netusb/setRepeat?mode=" + value);
1186     }
1187
1188     private @Nullable String setShuffle(String value, @Nullable String host) {
1189         return makeRequest("Shuffle", host + YAMAHA_EXTENDED_CONTROL + "netusb/setShuffle?mode=" + value);
1190     }
1191
1192     // End Net Radio/USB Related
1193
1194     // Start Music Cast API calls
1195     private @Nullable String getDistributionInfo(@Nullable String host) {
1196         return makeRequest("DistributionInfo", host + YAMAHA_EXTENDED_CONTROL + "dist/getDistributionInfo");
1197     }
1198
1199     private @Nullable String setClientServerInfo(@Nullable String host, String json, String type) {
1200         InputStream is = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
1201         try {
1202             url = "http://" + host + YAMAHA_EXTENDED_CONTROL + "dist/" + type;
1203             httpResponse = HttpUtil.executeUrl("POST", url, is, "", LONG_CONNECTION_TIMEOUT_MILLISEC);
1204             logger.trace("MC Link/Unlink Client {}", httpResponse);
1205             return httpResponse;
1206         } catch (IOException e) {
1207             logger.trace("IO Exception - {} - {}", type, e.getMessage());
1208             return "{\"response_code\":\"999\"}";
1209         }
1210     }
1211
1212     private @Nullable String startDistribution(@Nullable String host) {
1213         Random ran = new Random();
1214         int nxt = ran.nextInt(200000);
1215         return makeRequest("StartDistribution", host + YAMAHA_EXTENDED_CONTROL + "dist/startDistribution?num=" + nxt);
1216     }
1217
1218     // End Music Cast API calls
1219
1220     // Start General/System API calls
1221
1222     private @Nullable String getFeatures(@Nullable String host) {
1223         return makeRequest("Features", host + YAMAHA_EXTENDED_CONTROL + "system/getFeatures");
1224     }
1225
1226     private @Nullable String getDeviceInfo(@Nullable String host) {
1227         return makeRequest("DeviceInfo", host + YAMAHA_EXTENDED_CONTROL + "system/getDeviceInfo");
1228     }
1229
1230     private void keepUdpEventsAlive(@Nullable String host) {
1231         Properties appProps = new Properties();
1232         appProps.setProperty("X-AppName", "MusicCast/1");
1233         appProps.setProperty("X-AppPort", "41100");
1234         try {
1235             httpResponse = HttpUtil.executeUrl("GET", HTTP + host + YAMAHA_EXTENDED_CONTROL + "netusb/getPlayInfo",
1236                     appProps, null, "", LONG_CONNECTION_TIMEOUT_MILLISEC);
1237             // logger.trace("{}", httpResponse);
1238             logger.trace("{} - {}", "UDP task", httpResponse);
1239         } catch (IOException e) {
1240             logger.trace("UDP refresh failed - {}", e.getMessage());
1241         }
1242     }
1243     // End General/System API calls
1244 }