{"action":"created","comment":{"url":"https://api.github.com/repos/openhab/openhab/pulls/comments/48449644","id":48449644,"diff_hunk":"@@ -0,0 +1,447 @@\n+/**\n+ * Copyright (c) 2010-2015, openHAB.org and others.\n+ *\n+ * All rights reserved. This program and the accompanying materials\n+ * are made available under the terms of the Eclipse Public License v1.0\n+ * which accompanies this distribution, and is available at\n+ * http://www.eclipse.org/legal/epl-v10.html\n+ */\n+package org.openhab.binding.sonance.internal;\n+\n+import java.io.BufferedReader;\n+import java.io.DataOutputStream;\n+import java.io.IOException;\n+import java.io.InputStreamReader;\n+import java.net.Socket;\n+import java.net.UnknownHostException;\n+import java.util.ArrayList;\n+import java.util.HashMap;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.concurrent.locks.ReentrantLock;\n+import java.util.regex.Matcher;\n+import java.util.regex.Pattern;\n+\n+import org.openhab.binding.sonance.SonanceBindingProvider;\n+\n+import org.apache.commons.lang.StringUtils;\n+import org.openhab.core.binding.AbstractActiveBinding;\n+import org.openhab.core.binding.BindingProvider;\n+import org.openhab.core.library.types.DecimalType;\n+import org.openhab.core.library.types.IncreaseDecreaseType;\n+import org.openhab.core.library.types.OnOffType;\n+import org.openhab.core.types.Command;\n+import org.openhab.core.types.State;\n+import org.osgi.framework.BundleContext;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+/**\n+ * Implement this class if you are going create an actively polling service like\n+ * querying a Website/Device.\n+ * \n+ * @author Laurens Van Acker\n+ * @since 1.8.0\n+ */\n+public class SonanceBinding extends\n+\t\tAbstractActiveBinding<SonanceBindingProvider> {\n+\tprivate Map<String, Socket> socketCache = new HashMap<String, Socket>();\n+\tprivate Map<String, DataOutputStream> outputStreamCache = new HashMap<String, DataOutputStream>();\n+\tprivate Map<String, BufferedReader> bufferedReaderCache = new HashMap<String, BufferedReader>();\n+\tprivate Map<String, ReentrantLock> volumeLocks = new HashMap<String, ReentrantLock>();\n+\n+\tprivate static final Logger logger = LoggerFactory\n+\t\t\t.getLogger(SonanceBinding.class);\n+\n+\t/**\n+\t * the refresh interval which is used to poll values from the Sonance server\n+\t * (optional, defaults to 60000ms)\n+\t */\n+\tprivate long refreshInterval = 60000;\n+\n+\tpublic SonanceBinding() {\n+\t}\n+\n+\t/**\n+\t * Called by the SCR to activate the component with its configuration read\n+\t * from CAS\n+\t * \n+\t * @param bundleContext\n+\t *            BundleContext of the Bundle that defines this component\n+\t * @param configuration\n+\t *            Configuration properties for this component obtained from the\n+\t *            ConfigAdmin service\n+\t */\n+\tpublic void activate(final BundleContext bundleContext,\n+\t\t\tfinal Map<String, Object> configuration) {\n+\t\t// the configuration is guaranteed not to be null, because the component\n+\t\t// definition has the\n+\t\t// configuration-policy set to require. If set to 'optional' then the\n+\t\t// configuration may be null\n+\n+\t\t// to override the default refresh interval one has to add a\n+\t\t// parameter to openhab.cfg like <bindingName>:refresh=<intervalInMs>\n+\t\tString refreshIntervalString = (String) configuration.get(\"refresh\");\n+\t\tif (StringUtils.isNotBlank(refreshIntervalString))\n+\t\t\trefreshInterval = Long.parseLong(refreshIntervalString);\n+\n+\t\tsetProperlyConfigured(true);\n+\t}\n+\n+\t/**\n+\t * Called by the SCR when the configuration of a binding has been changed\n+\t * through the ConfigAdmin service.\n+\t * \n+\t * @param configuration\n+\t *            Updated configuration properties\n+\t */\n+\tpublic void modified(final Map<String, Object> configuration) {\n+\t\tString refreshIntervalString = (String) configuration.get(\"refresh\");\n+\t\tif (StringUtils.isNotBlank(refreshIntervalString))\n+\t\t\trefreshInterval = Long.parseLong(refreshIntervalString);\n+\t}\n+\n+\t/**\n+\t * Called by the SCR to deactivate the component when either the\n+\t * configuration is removed or mandatory references are no longer satisfied\n+\t * or the component has simply been stopped.\n+\t * \n+\t * @param reason\n+\t *            Reason code for the deactivation:<br>\n+\t *            <ul>\n+\t *            <li>0 – Unspecified\n+\t *            <li>1 – The component was disabled\n+\t *            <li>2 – A reference became unsatisfied\n+\t *            <li>3 – A configuration was changed\n+\t *            <li>4 – A configuration was deleted\n+\t *            <li>5 – The component was disposed\n+\t *            <li>6 – The bundle was stopped\n+\t *            </ul>\n+\t */\n+\tpublic void deactivate(final int reason) {\n+\t\t// deallocate resources here that are no longer needed and\n+\t\t// should be reset when activating this binding again\n+\t\tsocketCache.clear();\n+\t\toutputStreamCache.clear();\n+\t\tbufferedReaderCache.clear();\n+\t}\n+\n+\t@Override\n+\tprotected long getRefreshInterval() {\n+\t\treturn refreshInterval;\n+\t}\n+\n+\t@Override\n+\tprotected String getName() {\n+\t\treturn \"Sonance Refresh Service\";\n+\t}\n+\n+\t@Override\n+\tprotected void execute() {\n+\t\tif (!bindingsExist()) {\n+\t\t\tlogger.debug(\"There is no existing Sonance binding configuration => refresh cycle aborted!\");\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tlogger.info(\"Refreshing all items\");\n+\n+\t\tList<String> offlineEndPoints = new ArrayList<String>();\n+\n+\t\tfor (SonanceBindingProvider provider : providers) {\n+\t\t\tfor (String itemName : provider.getItemNames()) {\n+\t\t\t\tString group = provider.getGroup(itemName);\n+\t\t\t\tString ip = provider.getIP(itemName);\n+\t\t\t\tint port = provider.getPort(itemName);\n+\n+\t\t\t\tString key = ip + \":\" + port;\n+\t\t\t\tif (!offlineEndPoints.contains(key)) {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\tif (!socketCache.containsKey(key)) {\n+\t\t\t\t\t\t\tsocketCache.put(key, new Socket(ip, port));\n+\t\t\t\t\t\t\toutputStreamCache.put(key, new DataOutputStream(\n+\t\t\t\t\t\t\t\t\tsocketCache.get(key).getOutputStream()));\n+\t\t\t\t\t\t\tbufferedReaderCache.put(key, new BufferedReader(\n+\t\t\t\t\t\t\t\t\tnew InputStreamReader(socketCache.get(key)\n+\t\t\t\t\t\t\t\t\t\t\t.getInputStream())));\n+\t\t\t\t\t\t\tlogger.debug(\"New socket created\");\n+\t\t\t\t\t\t}\n+\n+\t\t\t\t\t\tif (provider.isMute(itemName))\n+\t\t\t\t\t\t\tsendMuteCommand(itemName, \"FF550212\" + group,\n+\t\t\t\t\t\t\t\t\toutputStreamCache.get(key),\n+\t\t\t\t\t\t\t\t\tbufferedReaderCache.get(key));\n+\t\t\t\t\t\telse if (provider.isVolume(itemName))\n+\t\t\t\t\t\t\tsendVolumeCommand(itemName, \"FF550210\" + group,\n+\t\t\t\t\t\t\t\t\toutputStreamCache.get(key),\n+\t\t\t\t\t\t\t\t\tbufferedReaderCache.get(key));\n+\t\t\t\t\t} catch (UnknownHostException e) {\n+\t\t\t\t\t\tlogger.error(\"UnknownHostException occured.\");\n+\t\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\t\tlogger.debug(\"Amplifier is offline, status can't be updated at this moment.\");\n+\t\t\t\t\t\ttry {\n+\t\t\t\t\t\t\tsocketCache.get(key).close();\n+\t\t\t\t\t\t} catch (Exception ex) {\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\tsocketCache.remove(key);\n+\t\t\t\t\t\toutputStreamCache.remove(key);\n+\t\t\t\t\t\tbufferedReaderCache.remove(key);\n+\t\t\t\t\t\tofflineEndPoints.add(key); // Stop trying to fetch other\n+\t\t\t\t\t\t\t\t\t\t\t\t\t// values from this end\n+\t\t\t\t\t\t\t\t\t\t\t\t\t// point until next execute\n+\t\t\t\t\t\t\t\t\t\t\t\t\t// cycle\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic void bindingChanged(BindingProvider provider, String itemName) {\n+\t\tsuper.bindingChanged(provider, itemName);\n+\n+\t\tfor (Map.Entry<String, Socket> entry : socketCache.entrySet())\n+\t\t\ttry {\n+\t\t\t\tentry.getValue().close();\n+\t\t\t} catch (IOException e) {\n+\t\t\t\tlogger.error(\"Can't close a socket when binding changed.\");\n+\t\t\t}\n+\n+\t\t// Cleanup all sockets\n+\t\tsocketCache.clear();\n+\t\toutputStreamCache.clear();\n+\t\tbufferedReaderCache.clear();\n+\t}\n+\n+\t@Override\n+\tprotected void internalReceiveCommand(String itemName, Command command) {\n+\t\tlogger.info(\"Command received ({}, {})\", itemName, command);\n+\n+\t\tSonanceBindingProvider provider = findFirstMatchingBindingProvider(itemName);\n+\t\tString group = provider.getGroup(itemName);\n+\t\tString ip = provider.getIP(itemName);\n+\t\tint port = provider.getPort(itemName);\n+\n+\t\tSocket s = null;\n+\t\ttry {\n+\t\t\ts = new Socket(ip, port);\n+\t\t\tDataOutputStream outToServer = new DataOutputStream(\n+\t\t\t\t\ts.getOutputStream());\n+\t\t\tBufferedReader i = new BufferedReader(new InputStreamReader(\n+\t\t\t\t\ts.getInputStream()));\n+\n+\t\t\tif (provider.isMute(itemName))\n+\t\t\t\tswitch (command.toString()) {\n+\t\t\t\tcase \"OFF\":\n+\t\t\t\t\tsendMuteCommand(itemName, \"FF550207\" + group, outToServer,\n+\t\t\t\t\t\t\ti); // Mute on\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase \"ON\":\n+\t\t\t\t\tsendMuteCommand(itemName, \"FF550208\" + group, outToServer,\n+\t\t\t\t\t\t\ti); // Mute off\n+\t\t\t\t\tbreak;\n+\t\t\t\tdefault:\n+\t\t\t\t\tlogger.error(\"I don't know what to do with this command\");\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\telse if (provider.isVolume(itemName))\n+\t\t\t\tif (command.toString().equals(\"UP\"))\n+\t\t\t\t\tsendVolumeCommand(itemName, \"FF550204\" + group,\n+\t\t\t\t\t\t\toutToServer, i);\n+\t\t\t\telse if (command.toString().equals(\"DOWN\"))\n+\t\t\t\t\tsendVolumeCommand(itemName, \"FF550205\" + group,\n+\t\t\t\t\t\t\toutToServer, i);\n+\t\t\t\telse {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\tsetVolumeCommand(itemName, group,\n+\t\t\t\t\t\t\t\tInteger.parseInt(command.toString()),\n+\t\t\t\t\t\t\t\toutToServer, i, ip + \":\" + port);\n+\t\t\t\t\t} catch (NumberFormatException nfe) {\n+\t\t\t\t\t\tlogger.error(\"I don't know what to do with this command\");\n+\t\t\t\t\t}\n+\n+\t\t\t\t}\n+\t\t\ts.close();\n+\t\t} catch (IOException e) {\n+\t\t\tlogger.debug(\"IO Exception when sending command\");\n+\t\t} finally {\n+\t\t\ttry {\n+\t\t\t\tif (s != null)\n+\t\t\t\t\ts.close();\n+\t\t\t} catch (IOException e) {\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tprotected void internalReceiveUpdate(String itemName, State newState) {\n+\t\tlogger.info(\"Update received ({},{})\", itemName, newState);\n+\n+\t\tSonanceBindingProvider provider = findFirstMatchingBindingProvider(itemName);\n+\t\tString group = provider.getGroup(itemName);\n+\t\tString ip = provider.getIP(itemName);\n+\t\tint port = provider.getPort(itemName);\n+\n+\t\tip = null;\n+\t\tgroup = null; // cleanup\n+\n+\t\tSocket s = null;\n+\t\ttry {\n+\t\t\ts = new Socket(ip, port);\n+\t\t\tDataOutputStream outToServer = new DataOutputStream(\n+\t\t\t\t\ts.getOutputStream());\n+\t\t\tBufferedReader i = new BufferedReader(new InputStreamReader(\n+\t\t\t\t\ts.getInputStream()));\n+\n+\t\t\tif (provider.isMute(itemName))\n+\t\t\t\tif (newState.equals(OnOffType.OFF))\n+\t\t\t\t\tsendMuteCommand(itemName, \"FF550207\" + group, outToServer,\n+\t\t\t\t\t\t\ti); // Mute on\n+\t\t\t\telse if (newState.equals(OnOffType.ON))\n+\t\t\t\t\tsendMuteCommand(itemName, \"FF550208\" + group, outToServer,\n+\t\t\t\t\t\t\ti); // Mute off\n+\t\t\t\telse\n+\t\t\t\t\tlogger.error(\"I don't know what to do with this command\");\n+\t\t\telse if (provider.isVolume(itemName))\n+\t\t\t\tif (newState.equals(IncreaseDecreaseType.INCREASE))\n+\t\t\t\t\tsendVolumeCommand(itemName, \"FF550204\" + group,\n+\t\t\t\t\t\t\toutToServer, i);\n+\t\t\t\telse if (newState.equals(IncreaseDecreaseType.DECREASE))\n+\t\t\t\t\tsendVolumeCommand(itemName, \"FF550205\" + group,\n+\t\t\t\t\t\t\toutToServer, i);\n+\t\t\t\telse\n+\t\t\t\t\tlogger.error(\"I don't know what to do with this command: \"\n+\t\t\t\t\t\t\t+ newState);\n+\t\t\ts.close();\n+\t\t} catch (IOException e) {\n+\t\t\tlogger.error(\"IO Exception\");\n+\t\t} finally {\n+\t\t\ttry {\n+\t\t\t\tif (s != null)\n+\t\t\t\t\ts.close();\n+\t\t\t} catch (IOException e) {\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tprivate void sendVolumeCommand(String itemName, String command,\n+\t\t\tDataOutputStream outToServer, BufferedReader i) throws IOException {\n+\t\tchar[] cbuf = new char[50]; // Response is always 50 characters\n+\n+\t\toutToServer.write(hexStringToByteArray(command));\n+\t\ti.read(cbuf, 0, 50);\n+\n+\t\tPattern p = Pattern.compile(\".*Vol=(-?\\\\d{1,2}).*\");\n+\t\tMatcher m = p.matcher(new String(cbuf));\n+\n+\t\tif (m.find())\n+\t\t\teventPublisher.postUpdate(itemName, new DecimalType(m.group(1)));\n+\t\telse\n+\t\t\tlogger.error(\"Error sending command, received this: \"\n+\t\t\t\t\t+ new String(cbuf));\n+\t}\n+\n+\tprivate void sendMuteCommand(String itemName, String command,\n+\t\t\tDataOutputStream outToServer, BufferedReader i) throws IOException {\n+\t\tchar[] cbuf = new char[50]; // Response is always 50 characters\n+\n+\t\t// logger.debug(\"Sending command {}\", command);\n+\t\toutToServer.write(hexStringToByteArray(command));\n+\t\ti.read(cbuf, 0, 50);\n+\n+\t\tString result = new String(cbuf);\n+\n+\t\tif (result.contains(\"Mute=on\") || result.contains(\"MuteOn\"))\n+\t\t\teventPublisher.postUpdate(itemName, OnOffType.OFF);\n+\t\telse if (result.contains(\"Mute=off\") || result.contains(\"MuteOff\"))\n+\t\t\teventPublisher.postUpdate(itemName, OnOffType.ON);\n+\t\telse\n+\t\t\tlogger.error(\"Error sending command, received this: \"\n+\t\t\t\t\t+ new String(cbuf));\n+\t}\n+\n+\tprivate void setVolumeCommand(String itemName, String group,\n+\t\t\tint targetVolume, DataOutputStream outToServer, BufferedReader i,\n+\t\t\tString endpoint) throws IOException {\n+\t\tchar[] cbuf = new char[50]; // Response is always 50 characters\n+\n+\t\t// Now lock this part, so we don't end up with two functions going up\n+\t\t// and now all the time\n+\t\tString lockKey = endpoint + \":\" + group;\n+\t\tif (!volumeLocks.containsKey(lockKey))\n+\t\t\tvolumeLocks.put(lockKey, new ReentrantLock()); // We can keep this\n+\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// one when we\n+\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// finished with it,\n+\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// speed before\n+\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// memory usage\n+\t\tvolumeLocks.get(lockKey).lock();\n+\n+\t\ttry {\n+\t\t\toutToServer.write(hexStringToByteArray(\"FF550210\" + group));\n+\t\t\ti.read(cbuf, 0, 50);\n+\n+\t\t\tPattern p = Pattern.compile(\".*Vol=(-?\\\\d{1,2}).*\");\n+\t\t\tMatcher m = p.matcher(new String(cbuf));\n+\n+\t\t\tif (m.find()) {\n+\t\t\t\tdouble currentVolume = Integer.parseInt(m.group(1));\n+\t\t\t\teventPublisher.postUpdate(itemName, new DecimalType(\n+\t\t\t\t\t\tcurrentVolume));\n+\t\t\t\tint step = 0; // We should be able to reach every volume in less\n+\t\t\t\t\t\t\t\t// 29 steps\n+\t\t\t\twhile (currentVolume != targetVolume && step++ <= 28) {\n+\t\t\t\t\tlogger.debug(\n+\t\t\t\t\t\t\t\"Current volume: {}, target volume: {}, step: {})\",\n+\t\t\t\t\t\t\tcurrentVolume, targetVolume, step);\n+\t\t\t\t\tif (currentVolume + 3 <= targetVolume)\n+\t\t\t\t\t\toutToServer.write(hexStringToByteArray(\"FF55020E\"\n+\t\t\t\t\t\t\t\t+ group)); // Volume UP +3\n+\t\t\t\t\tif (currentVolume - 3 >= targetVolume)\n+\t\t\t\t\t\toutToServer.write(hexStringToByteArray(\"FF55020F\"\n+\t\t\t\t\t\t\t\t+ group)); // Volume Down -3\n+\t\t\t\t\telse if (currentVolume < targetVolume)\n+\t\t\t\t\t\toutToServer.write(hexStringToByteArray(\"FF550204\"\n+\t\t\t\t\t\t\t\t+ group)); // Volume UP\n+\t\t\t\t\telse\n+\t\t\t\t\t\toutToServer.write(hexStringToByteArray(\"FF550205\"\n+\t\t\t\t\t\t\t\t+ group)); // Volume Down\n+\t\t\t\t\ti.read(cbuf, 0, 50);\n+\t\t\t\t\tm = p.matcher(new String(cbuf));\n+\t\t\t\t\tif (m.find()) {\n+\t\t\t\t\t\tcurrentVolume = Integer.parseInt(m.group(1));\n+\t\t\t\t\t\tlogger.info(\"Setting volume, current volume : \"\n+\t\t\t\t\t\t\t\t+ currentVolume);\n+\t\t\t\t\t\teventPublisher.postUpdate(itemName, new DecimalType(\n+\t\t\t\t\t\t\t\tcurrentVolume));\n+\t\t\t\t\t} else\n+\t\t\t\t\t\tlogger.error(\"Error sending command, received this: \"\n+\t\t\t\t\t\t\t\t+ new String(cbuf));\n+\t\t\t\t}\n+\t\t\t} else\n+\t\t\t\tlogger.error(\"Error sending command, received this: \"\n+\t\t\t\t\t\t+ new String(cbuf));\n+\t\t} finally {\n+\t\t\tvolumeLocks.get(lockKey).unlock();\n+\t\t}\n+\t}\n+\n+\tprotected SonanceBindingProvider findFirstMatchingBindingProvider(\n+\t\t\tString itemName) {\n+\t\tSonanceBindingProvider firstMatchingProvider = null;\n+\t\tfor (SonanceBindingProvider provider : providers) {\n+\t\t\tif (provider.providesBindingFor(itemName)) {\n+\t\t\t\tfirstMatchingProvider = provider;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\treturn firstMatchingProvider;\n+\t}\n+\n+\tprivate static byte[] hexStringToByteArray(String s) {\n+\t\tint len = s.length();\n+\t\tbyte[] data = new byte[len / 2];\n+\t\tfor (int i = 0; i < len; i += 2)\n+\t\t\tdata[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character\n+\t\t\t\t\t.digit(s.charAt(i + 1), 16));\n+\t\treturn data;\n+\t}\n+}","path":"bundles/binding/org.openhab.binding.sonance/src/main/java/org/openhab/binding/sonance/internal/SonanceBinding.java","position":447,"original_position":447,"commit_id":"bfbbc3dab088169b46a77d152af7efaaa6d4a53b","original_commit_id":"bfbbc3dab088169b46a77d152af7efaaa6d4a53b","user":{"login":"teichsta","id":2108636,"avatar_url":"https://avatars.githubusercontent.com/u/2108636?v=3","gravatar_id":"","url":"https://api.github.com/users/teichsta","html_url":"https://github.com/teichsta","followers_url":"https://api.github.com/users/teichsta/followers","following_url":"https://api.github.com/users/teichsta/following{/other_user}","gists_url":"https://api.github.com/users/teichsta/gists{/gist_id}","starred_url":"https://api.github.com/users/teichsta/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/teichsta/subscriptions","organizations_url":"https://api.github.com/users/teichsta/orgs","repos_url":"https://api.github.com/users/teichsta/repos","events_url":"https://api.github.com/users/teichsta/events{/privacy}","received_events_url":"https://api.github.com/users/teichsta/received_events","type":"User","site_admin":false},"body":"add new line","created_at":"2015-12-26T20:28:08Z","updated_at":"2015-12-26T20:28:08Z","html_url":"https://github.com/openhab/openhab/pull/3401#discussion_r48449644","pull_request_url":"https://api.github.com/repos/openhab/openhab/pulls/3401","_links":{"self":{"href":"https://api.github.com/repos/openhab/openhab/pulls/comments/48449644"},"html":{"href":"https://github.com/openhab/openhab/pull/3401#discussion_r48449644"},"pull_request":{"href":"https://api.github.com/repos/openhab/openhab/pulls/3401"}}},"pull_request":{"url":"https://api.github.com/repos/openhab/openhab/pulls/3401","id":50107542,"html_url":"https://github.com/openhab/openhab/pull/3401","diff_url":"https://github.com/openhab/openhab/pull/3401.diff","patch_url":"https://github.com/openhab/openhab/pull/3401.patch","issue_url":"https://api.github.com/repos/openhab/openhab/issues/3401","number":3401,"state":"open","locked":false,"title":"Sonance binding","user":{"login":"LaurensVanAcker","id":4385000,"avatar_url":"https://avatars.githubusercontent.com/u/4385000?v=3","gravatar_id":"","url":"https://api.github.com/users/LaurensVanAcker","html_url":"https://github.com/LaurensVanAcker","followers_url":"https://api.github.com/users/LaurensVanAcker/followers","following_url":"https://api.github.com/users/LaurensVanAcker/following{/other_user}","gists_url":"https://api.github.com/users/LaurensVanAcker/gists{/gist_id}","starred_url":"https://api.github.com/users/LaurensVanAcker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LaurensVanAcker/subscriptions","organizations_url":"https://api.github.com/users/LaurensVanAcker/orgs","repos_url":"https://api.github.com/users/LaurensVanAcker/repos","events_url":"https://api.github.com/users/LaurensVanAcker/events{/privacy}","received_events_url":"https://api.github.com/users/LaurensVanAcker/received_events","type":"User","site_admin":false},"body":"Binding to integrate Sonance DSP 2-150, DSP 8-130 and DSP 2-750\r\namplifiers with OpenHAB\r\n\r\nSigned-off-by: Laurens Van Acker laurens@van.acker.be","created_at":"2015-11-09T10:45:33Z","updated_at":"2015-12-26T20:28:08Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b25922420584f279f73c9e95cd9f76a0475f294f","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/openhab/openhab/pulls/3401/commits","review_comments_url":"https://api.github.com/repos/openhab/openhab/pulls/3401/comments","review_comment_url":"https://api.github.com/repos/openhab/openhab/pulls/comments{/number}","comments_url":"https://api.github.com/repos/openhab/openhab/issues/3401/comments","statuses_url":"https://api.github.com/repos/openhab/openhab/statuses/bfbbc3dab088169b46a77d152af7efaaa6d4a53b","head":{"label":"LaurensVanAcker:Sonance-binding","ref":"Sonance-binding","sha":"bfbbc3dab088169b46a77d152af7efaaa6d4a53b","user":{"login":"LaurensVanAcker","id":4385000,"avatar_url":"https://avatars.githubusercontent.com/u/4385000?v=3","gravatar_id":"","url":"https://api.github.com/users/LaurensVanAcker","html_url":"https://github.com/LaurensVanAcker","followers_url":"https://api.github.com/users/LaurensVanAcker/followers","following_url":"https://api.github.com/users/LaurensVanAcker/following{/other_user}","gists_url":"https://api.github.com/users/LaurensVanAcker/gists{/gist_id}","starred_url":"https://api.github.com/users/LaurensVanAcker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LaurensVanAcker/subscriptions","organizations_url":"https://api.github.com/users/LaurensVanAcker/orgs","repos_url":"https://api.github.com/users/LaurensVanAcker/repos","events_url":"https://api.github.com/users/LaurensVanAcker/events{/privacy}","received_events_url":"https://api.github.com/users/LaurensVanAcker/received_events","type":"User","site_admin":false},"repo":{"id":45831453,"name":"openhab","full_name":"LaurensVanAcker/openhab","owner":{"login":"LaurensVanAcker","id":4385000,"avatar_url":"https://avatars.githubusercontent.com/u/4385000?v=3","gravatar_id":"","url":"https://api.github.com/users/LaurensVanAcker","html_url":"https://github.com/LaurensVanAcker","followers_url":"https://api.github.com/users/LaurensVanAcker/followers","following_url":"https://api.github.com/users/LaurensVanAcker/following{/other_user}","gists_url":"https://api.github.com/users/LaurensVanAcker/gists{/gist_id}","starred_url":"https://api.github.com/users/LaurensVanAcker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LaurensVanAcker/subscriptions","organizations_url":"https://api.github.com/users/LaurensVanAcker/orgs","repos_url":"https://api.github.com/users/LaurensVanAcker/repos","events_url":"https://api.github.com/users/LaurensVanAcker/events{/privacy}","received_events_url":"https://api.github.com/users/LaurensVanAcker/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/LaurensVanAcker/openhab","description":"Source code of the open Home Automation Bus (openHAB)","fork":true,"url":"https://api.github.com/repos/LaurensVanAcker/openhab","forks_url":"https://api.github.com/repos/LaurensVanAcker/openhab/forks","keys_url":"https://api.github.com/repos/LaurensVanAcker/openhab/keys{/key_id}","collaborators_url":"https://api.github.com/repos/LaurensVanAcker/openhab/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/LaurensVanAcker/openhab/teams","hooks_url":"https://api.github.com/repos/LaurensVanAcker/openhab/hooks","issue_events_url":"https://api.github.com/repos/LaurensVanAcker/openhab/issues/events{/number}","events_url":"https://api.github.com/repos/LaurensVanAcker/openhab/events","assignees_url":"https://api.github.com/repos/LaurensVanAcker/openhab/assignees{/user}","branches_url":"https://api.github.com/repos/LaurensVanAcker/openhab/branches{/branch}","tags_url":"https://api.github.com/repos/LaurensVanAcker/openhab/tags","blobs_url":"https://api.github.com/repos/LaurensVanAcker/openhab/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/LaurensVanAcker/openhab/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/LaurensVanAcker/openhab/git/refs{/sha}","trees_url":"https://api.github.com/repos/LaurensVanAcker/openhab/git/trees{/sha}","statuses_url":"https://api.github.com/repos/LaurensVanAcker/openhab/statuses/{sha}","languages_url":"https://api.github.com/repos/LaurensVanAcker/openhab/languages","stargazers_url":"https://api.github.com/repos/LaurensVanAcker/openhab/stargazers","contributors_url":"https://api.github.com/repos/LaurensVanAcker/openhab/contributors","subscribers_url":"https://api.github.com/repos/LaurensVanAcker/openhab/subscribers","subscription_url":"https://api.github.com/repos/LaurensVanAcker/openhab/subscription","commits_url":"https://api.github.com/repos/LaurensVanAcker/openhab/commits{/sha}","git_commits_url":"https://api.github.com/repos/LaurensVanAcker/openhab/git/commits{/sha}","comments_url":"https://api.github.com/repos/LaurensVanAcker/openhab/comments{/number}","issue_comment_url":"https://api.github.com/repos/LaurensVanAcker/openhab/issues/comments{/number}","contents_url":"https://api.github.com/repos/LaurensVanAcker/openhab/contents/{+path}","compare_url":"https://api.github.com/repos/LaurensVanAcker/openhab/compare/{base}...{head}","merges_url":"https://api.github.com/repos/LaurensVanAcker/openhab/merges","archive_url":"https://api.github.com/repos/LaurensVanAcker/openhab/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/LaurensVanAcker/openhab/downloads","issues_url":"https://api.github.com/repos/LaurensVanAcker/openhab/issues{/number}","pulls_url":"https://api.github.com/repos/LaurensVanAcker/openhab/pulls{/number}","milestones_url":"https://api.github.com/repos/LaurensVanAcker/openhab/milestones{/number}","notifications_url":"https://api.github.com/repos/LaurensVanAcker/openhab/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/LaurensVanAcker/openhab/labels{/name}","releases_url":"https://api.github.com/repos/LaurensVanAcker/openhab/releases{/id}","created_at":"2015-11-09T10:34:37Z","updated_at":"2015-11-09T10:34:58Z","pushed_at":"2015-11-10T15:35:47Z","git_url":"git://github.com/LaurensVanAcker/openhab.git","ssh_url":"git@github.com:LaurensVanAcker/openhab.git","clone_url":"https://github.com/LaurensVanAcker/openhab.git","svn_url":"https://github.com/LaurensVanAcker/openhab","homepage":null,"size":161626,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"openhab:master","ref":"master","sha":"a93ec94ecabf3db35cc62783298f0c426ae3d09a","user":{"login":"openhab","id":1007353,"avatar_url":"https://avatars.githubusercontent.com/u/1007353?v=3","gravatar_id":"","url":"https://api.github.com/users/openhab","html_url":"https://github.com/openhab","followers_url":"https://api.github.com/users/openhab/followers","following_url":"https://api.github.com/users/openhab/following{/other_user}","gists_url":"https://api.github.com/users/openhab/gists{/gist_id}","starred_url":"https://api.github.com/users/openhab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openhab/subscriptions","organizations_url":"https://api.github.com/users/openhab/orgs","repos_url":"https://api.github.com/users/openhab/repos","events_url":"https://api.github.com/users/openhab/events{/privacy}","received_events_url":"https://api.github.com/users/openhab/received_events","type":"Organization","site_admin":false},"repo":{"id":12983151,"name":"openhab","full_name":"openhab/openhab","owner":{"login":"openhab","id":1007353,"avatar_url":"https://avatars.githubusercontent.com/u/1007353?v=3","gravatar_id":"","url":"https://api.github.com/users/openhab","html_url":"https://github.com/openhab","followers_url":"https://api.github.com/users/openhab/followers","following_url":"https://api.github.com/users/openhab/following{/other_user}","gists_url":"https://api.github.com/users/openhab/gists{/gist_id}","starred_url":"https://api.github.com/users/openhab/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openhab/subscriptions","organizations_url":"https://api.github.com/users/openhab/orgs","repos_url":"https://api.github.com/users/openhab/repos","events_url":"https://api.github.com/users/openhab/events{/privacy}","received_events_url":"https://api.github.com/users/openhab/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/openhab/openhab","description":"Source code of the open Home Automation Bus (openHAB)","fork":false,"url":"https://api.github.com/repos/openhab/openhab","forks_url":"https://api.github.com/repos/openhab/openhab/forks","keys_url":"https://api.github.com/repos/openhab/openhab/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openhab/openhab/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openhab/openhab/teams","hooks_url":"https://api.github.com/repos/openhab/openhab/hooks","issue_events_url":"https://api.github.com/repos/openhab/openhab/issues/events{/number}","events_url":"https://api.github.com/repos/openhab/openhab/events","assignees_url":"https://api.github.com/repos/openhab/openhab/assignees{/user}","branches_url":"https://api.github.com/repos/openhab/openhab/branches{/branch}","tags_url":"https://api.github.com/repos/openhab/openhab/tags","blobs_url":"https://api.github.com/repos/openhab/openhab/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openhab/openhab/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openhab/openhab/git/refs{/sha}","trees_url":"https://api.github.com/repos/openhab/openhab/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openhab/openhab/statuses/{sha}","languages_url":"https://api.github.com/repos/openhab/openhab/languages","stargazers_url":"https://api.github.com/repos/openhab/openhab/stargazers","contributors_url":"https://api.github.com/repos/openhab/openhab/contributors","subscribers_url":"https://api.github.com/repos/openhab/openhab/subscribers","subscription_url":"https://api.github.com/repos/openhab/openhab/subscription","commits_url":"https://api.github.com/repos/openhab/openhab/commits{/sha}","git_commits_url":"https://api.github.com/repos/openhab/openhab/git/commits{/sha}","comments_url":"https://api.github.com/repos/openhab/openhab/comments{/number}","issue_comment_url":"https://api.github.com/repos/openhab/openhab/issues/comments{/number}","contents_url":"https://api.github.com/repos/openhab/openhab/contents/{+path}","compare_url":"https://api.github.com/repos/openhab/openhab/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openhab/openhab/merges","archive_url":"https://api.github.com/repos/openhab/openhab/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openhab/openhab/downloads","issues_url":"https://api.github.com/repos/openhab/openhab/issues{/number}","pulls_url":"https://api.github.com/repos/openhab/openhab/pulls{/number}","milestones_url":"https://api.github.com/repos/openhab/openhab/milestones{/number}","notifications_url":"https://api.github.com/repos/openhab/openhab/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openhab/openhab/labels{/name}","releases_url":"https://api.github.com/repos/openhab/openhab/releases{/id}","created_at":"2013-09-20T20:28:43Z","updated_at":"2015-12-26T16:06:29Z","pushed_at":"2015-12-26T20:06:03Z","git_url":"git://github.com/openhab/openhab.git","ssh_url":"git@github.com:openhab/openhab.git","clone_url":"https://github.com/openhab/openhab.git","svn_url":"https://github.com/openhab/openhab","homepage":null,"size":246279,"stargazers_count":1983,"watchers_count":1983,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1112,"mirror_url":null,"open_issues_count":463,"forks":1112,"open_issues":463,"watchers":1983,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/openhab/openhab/pulls/3401"},"html":{"href":"https://github.com/openhab/openhab/pull/3401"},"issue":{"href":"https://api.github.com/repos/openhab/openhab/issues/3401"},"comments":{"href":"https://api.github.com/repos/openhab/openhab/issues/3401/comments"},"review_comments":{"href":"https://api.github.com/repos/openhab/openhab/pulls/3401/comments"},"review_comment":{"href":"https://api.github.com/repos/openhab/openhab/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/openhab/openhab/pulls/3401/commits"},"statuses":{"href":"https://api.github.com/repos/openhab/openhab/statuses/bfbbc3dab088169b46a77d152af7efaaa6d4a53b"}}}}