/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.mystromecopower.internal; import static org.quartz.TriggerBuilder.newTrigger; import java.io.IOException; import java.net.MalformedURLException; import java.util.Date; import java.util.Dictionary; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.openhab.binding.mystromecopower.MyStromEcoPowerBindingProvider; import org.openhab.binding.mystromecopower.internal.api.IMystromClient; import org.openhab.binding.mystromecopower.internal.api.MystromClient; import org.openhab.binding.mystromecopower.internal.api.mock.MockMystromClient; import org.openhab.binding.mystromecopower.internal.api.model.MystromDevice; import org.openhab.binding.mystromecopower.internal.util.ChangeStateJob; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The mystrom binding class. * * @author Jordens Christophe * @since 1.8.0 */ public class MyStromEcoPowerBinding extends AbstractActiveBinding<MyStromEcoPowerBindingProvider> implements ManagedService { /** * If set to true, use the mystrom client mock to simulate the mystrom * server. */ private Boolean devMode = false; /** * The user name to login on mystrom server. */ private String userName; /** * The password to login on mystrom server. */ private String password; /** * The mystrom client to call the mystrom server. */ private IMystromClient mystromClient; /** * The openhab logger. */ private static final Logger logger = LoggerFactory.getLogger(MyStromEcoPowerBinding.class); /** * List of discovered devices with their names and id. */ protected Map<String, String> devicesMap = new HashMap<String, String>(); /** * The master device found after discovery, necessary for restart command. */ private MystromDevice masterDevice; /** * The refresh interval which is used to poll values from the * MyStromEcoPower server (optional, defaults to 60000ms) */ private long refreshInterval = 60000; /** Holds the local quartz scheduler instance */ private Scheduler scheduler; /** * The default constructor. */ public MyStromEcoPowerBinding() { } @Override public void activate() { try { scheduler = StdSchedulerFactory.getDefaultScheduler(); super.activate(); } catch (SchedulerException se) { logger.error("initializing scheduler throws exception", se); } } @Override public void deactivate() { // deallocate resources here that are no longer needed and // should be reset when activating this binding again } /** * @{inheritDoc */ @Override protected long getRefreshInterval() { return refreshInterval; } /** * @{inheritDoc */ @Override protected String getName() { return "MyStromEcoPower Refresh Service"; } /** * @{inheritDoc */ @Override protected void execute() { // the frequently executed code (polling) goes here ... logger.debug("execute() method is called!"); if (this.devicesMap.isEmpty()) { return; } List<MystromDevice> devices = this.mystromClient.getDevicesState(); for (MyStromEcoPowerBindingProvider provider : providers) { for (String itemName : provider.getItemNames()) { logger.debug("Mystrom eco power switch '{}' state will be updated", itemName); String friendlyName = provider.getMystromFriendlyName(itemName); String id = this.devicesMap.get(friendlyName); if (id != null) { MystromDevice device = null; for (MystromDevice searchDevice : devices) { if (searchDevice.id.equals(id)) { device = searchDevice; break; } } if (device != null) { if (provider.getIsSwitch(itemName)) { State state = device.state.equals("on") ? OnOffType.ON : OnOffType.OFF; eventPublisher.postUpdate(itemName, state); } if (provider.getIsStringItem(itemName)) { // publish state of device, on/off/offline eventPublisher.postUpdate(itemName, new StringType(device.state)); } if (provider.getIsNumberItem(itemName)) { eventPublisher.postUpdate(itemName, new DecimalType(device.power)); } } } else { logger.warn("The device itemName '{}' not found on discovery. Verify device is not offline", itemName); } } } } /** * @{inheritDoc */ @Override protected void internalReceiveCommand(String itemName, Command command) { // the code being executed when a command was sent on the openHAB // event bus goes here. This method is only called if one of the // BindingProviders provide a binding for the given 'itemName'. logger.debug("internalReceiveCommand() is called!"); String deviceId = null; for (MyStromEcoPowerBindingProvider provider : providers) { String switchFriendlyName = provider.getMystromFriendlyName(itemName); deviceId = this.devicesMap.get(switchFriendlyName); logger.debug("item '{}' is configured as '{}'", itemName, switchFriendlyName); if (deviceId != null) { if (provider.getIsSwitch(itemName)) { try { logger.debug("Command '{}' is about to be sent to item '{}'", command, itemName); if (OnOffType.ON.equals(command) || OnOffType.OFF.equals(command)) { // on/off command boolean onOff = OnOffType.ON.equals(command); logger.debug("command '{}' transformed to '{}'", command, onOff ? "on" : "off"); boolean actualState = this.mystromClient.getDeviceInfo(deviceId).state.equals("on"); if (onOff == actualState) { // mystrom state is the same, may be due to // change state on/off too // rapidly, so postpone change state String scheduledCommand = deviceId + ";" + onOff; logger.debug("Schedule command: " + scheduledCommand); JobDetail job = JobBuilder .newJob(org.openhab.binding.mystromecopower.internal.util.ChangeStateJob.class) .usingJobData( org.openhab.binding.mystromecopower.internal.util.ChangeStateJob.JOB_DATA_CONTENT_KEY, scheduledCommand) .withIdentity(itemName, "MYSTROMECOPOWER").build(); Date dateTrigger = new Date(System.currentTimeMillis() + 5000L); Trigger trigger = newTrigger().forJob(job) .withIdentity(itemName + "_" + dateTrigger + "_trigger", "MYSTROMECOPOWER") .startAt(dateTrigger).build(); this.scheduler.scheduleJob(job, trigger); } else { if (this.masterDevice == null || (this.masterDevice != null && deviceId != this.masterDevice.id)) { // This is not the master device. if (!this.mystromClient.ChangeState(deviceId, onOff)) { // Unsuccessful state change, inform bus // that the good // state is the old one. eventPublisher.postUpdate(itemName, onOff ? OnOffType.OFF : OnOffType.ON); } } else { // This is the mater device. if (this.masterDevice != null && OnOffType.OFF.equals(command)) { // Do a reset if try to set OFF the // master device. logger.debug("Restart master device"); this.mystromClient.RestartMaster(deviceId); } } } } } catch (Exception e) { logger.error("Failed to send '{}' command", command, e); } } } else { logger.error("Unable to send command to '{}'. Device is not in discovery table", itemName); } } } /** * @{inheritDoc */ @Override protected void internalReceiveUpdate(String itemName, State newState) { // the code being executed when a state was sent on the openHAB // event bus goes here. This method is only called if one of the // BindingProviders provide a binding for the given 'itemName'. logger.debug("internalReceiveCommand() is called!"); } protected void addBindingProvider(MyStromEcoPowerBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(MyStromEcoPowerBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /** * {@inheritDoc} */ @Override public void updated(Dictionary<String, ?> config) throws ConfigurationException { if (config != null) { // to override the default refresh interval one has to add a // parameter to openhab.cfg like // <bindingName>:refresh=<intervalInMs> String refreshIntervalString = (String) config.get("refresh"); if (StringUtils.isNotBlank(refreshIntervalString)) { refreshInterval = Long.parseLong(refreshIntervalString); } // read further config parameters here ... // read user name String userName = (String) config.get("userName"); if (StringUtils.isNotBlank(userName)) { this.userName = userName; } else { throw new ConfigurationException("userName", "The userName to connect to myStrom must be specified in config file"); } String password = (String) config.get("password"); if (StringUtils.isNotBlank(password)) { this.password = password; } else { throw new ConfigurationException("password", "The password to connect to myStrom must be specified in config file"); } if (this.devMode) { this.mystromClient = new MockMystromClient(); } else { this.mystromClient = new MystromClient(this.userName, this.password, logger); } if (ChangeStateJob.MystromClient == null) { ChangeStateJob.MystromClient = this.mystromClient; } setProperlyConfigured(true); // do a discovery of all mystrom eco power to get id of devices try { this.mystromDiscovery(); } catch (IOException e) { logger.error("Error doing discovery of your devices", e); } } } /** * Do a discovery to find all devices on the mystrom server. Logs device's * name and id. * * @throws MalformedURLException * @throws IOException */ private void mystromDiscovery() throws MalformedURLException, IOException { List<MystromDevice> devices; logger.info("Do mystrom discovery"); this.devicesMap.clear(); if (this.mystromClient.login() == false) { logger.info("Invalid user or password"); return; } devices = this.mystromClient.getDevices(); for (MystromDevice mystromDevice : devices) { this.devicesMap.put(mystromDevice.name, mystromDevice.id); if (mystromDevice.type.equals("mst")) { this.masterDevice = mystromDevice; } logger.info("Mystrom device name: '{}', mystrom device id:'{}'", mystromDevice.name, mystromDevice.id); } } }