[light.tradfri] async support with resource observation. (#7815)

* [light.tradfri] Initial support for observe

* Update for pytradfri 2.0

* Fix imports

* Fix missing call

* Don't yield from add devices

* Fix imports

* Minor fixes to async code.

* Imports, formatting

* Docker updates, some minor async code changes.

* Lint

* Lint

* Update pytradfri

* Minor updates for release version

* Build fixes

* Retry observation if failed

* Revert

* Additional logging, fix returns

* Fix rename

* Bump version

* Bump version

* Support transitions

* Lint

* Fix transitions

* Update Dockerfile

* Set temp first

* Observation error handling

* Lint

* Lint

* Lint

* Merge upstream changes

* Fix bugs

* Fix bugs

* Fix bugs

* Lint

* Add sensor

* Add sensor

* Move sensor attrs

* Filter devices better

* Lint

* Address comments

* Pin aiocoap

* Fix bug if no devices

* Requirements
This commit is contained in:
Lewis Juggins 2017-10-05 17:05:38 +01:00 committed by Paulus Schoutsen
parent 89042439b8
commit 8db4641455
9 changed files with 336 additions and 91 deletions

View File

@ -11,9 +11,10 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
#ENV INSTALL_FFMPEG no #ENV INSTALL_FFMPEG no
#ENV INSTALL_LIBCEC no #ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no #ENV INSTALL_PHANTOMJS no
#ENV INSTALL_COAP_CLIENT no #ENV INSTALL_COAP no
#ENV INSTALL_SSOCR no #ENV INSTALL_SSOCR no
VOLUME /config VOLUME /config
RUN mkdir -p /usr/src/app RUN mkdir -p /usr/src/app
@ -25,7 +26,6 @@ RUN virtualization/Docker/setup_docker_prereqs
# Install hass component dependencies # Install hass component dependencies
COPY requirements_all.txt requirements_all.txt COPY requirements_all.txt requirements_all.txt
# Uninstall enum34 because some depenndecies install it but breaks Python 3.4+. # Uninstall enum34 because some depenndecies install it but breaks Python 3.4+.
# See PR #8103 for more info. # See PR #8103 for more info.
RUN pip3 install --no-cache-dir -r requirements_all.txt && \ RUN pip3 install --no-cache-dir -r requirements_all.txt && \

View File

@ -4,15 +4,18 @@ Support for the IKEA Tradfri platform.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.tradfri/ https://home-assistant.io/components/light.tradfri/
""" """
import asyncio
import logging import logging
from homeassistant.core import callback
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION,
SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, Light) SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, SUPPORT_COLOR_TEMP,
from homeassistant.components.light import ( SUPPORT_RGB_COLOR, Light)
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA) from homeassistant.components.light import \
from homeassistant.components.tradfri import ( PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA
KEY_GATEWAY, KEY_TRADFRI_GROUPS, KEY_API) from homeassistant.components.tradfri import KEY_GATEWAY, KEY_TRADFRI_GROUPS, \
KEY_API
from homeassistant.util import color as color_util from homeassistant.util import color as color_util
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -20,10 +23,13 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['tradfri'] DEPENDENCIES = ['tradfri']
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA
IKEA = 'IKEA of Sweden' IKEA = 'IKEA of Sweden'
TRADFRI_LIGHT_MANAGER = 'Tradfri Light Manager'
SUPPORTED_FEATURES = (SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION)
ALLOWED_TEMPERATURES = {IKEA} ALLOWED_TEMPERATURES = {IKEA}
def setup_platform(hass, config, add_devices, discovery_info=None): @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the IKEA Tradfri Light platform.""" """Set up the IKEA Tradfri Light platform."""
if discovery_info is None: if discovery_info is None:
return return
@ -31,14 +37,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
gateway_id = discovery_info['gateway'] gateway_id = discovery_info['gateway']
api = hass.data[KEY_API][gateway_id] api = hass.data[KEY_API][gateway_id]
gateway = hass.data[KEY_GATEWAY][gateway_id] gateway = hass.data[KEY_GATEWAY][gateway_id]
devices = api(gateway.get_devices())
lights = [dev for dev in devices if api(dev).has_light_control] devices_command = gateway.get_devices()
add_devices(Tradfri(light, api) for light in lights) devices_commands = yield from api(devices_command)
devices = yield from api(*devices_commands)
lights = [dev for dev in devices if dev.has_light_control]
if lights:
async_add_devices(TradfriLight(light, api) for light in lights)
allow_tradfri_groups = hass.data[KEY_TRADFRI_GROUPS][gateway_id] allow_tradfri_groups = hass.data[KEY_TRADFRI_GROUPS][gateway_id]
if allow_tradfri_groups: if allow_tradfri_groups:
groups = api(gateway.get_groups()) groups_command = gateway.get_groups()
add_devices(TradfriGroup(group, api) for group in groups) groups_commands = yield from api(groups_command)
groups = yield from api(*groups_commands)
if groups:
async_add_devices(TradfriGroup(group, api) for group in groups)
class TradfriGroup(Light): class TradfriGroup(Light):
@ -46,14 +59,26 @@ class TradfriGroup(Light):
def __init__(self, light, api): def __init__(self, light, api):
"""Initialize a Group.""" """Initialize a Group."""
self._group = api(light)
self._api = api self._api = api
self._name = self._group.name self._group = light
self._name = light.name
self._refresh(light)
@asyncio.coroutine
def async_added_to_hass(self):
"""Start thread when added to hass."""
self._async_start_observe()
@property
def should_poll(self):
"""No polling needed for tradfri group."""
return False
@property @property
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """Flag supported features."""
return SUPPORT_BRIGHTNESS return SUPPORTED_FEATURES
@property @property
def name(self): def name(self):
@ -70,49 +95,68 @@ class TradfriGroup(Light):
"""Return the brightness of the group lights.""" """Return the brightness of the group lights."""
return self._group.dimmer return self._group.dimmer
def turn_off(self, **kwargs): @asyncio.coroutine
def async_turn_off(self, **kwargs):
"""Instruct the group lights to turn off.""" """Instruct the group lights to turn off."""
self._api(self._group.set_state(0)) self.hass.async_add_job(self._api(self._group.set_state(0)))
def turn_on(self, **kwargs): @asyncio.coroutine
def async_turn_on(self, **kwargs):
"""Instruct the group lights to turn on, or dim.""" """Instruct the group lights to turn on, or dim."""
keys = {}
if ATTR_TRANSITION in kwargs:
keys['transition_time'] = int(kwargs[ATTR_TRANSITION])
if ATTR_BRIGHTNESS in kwargs: if ATTR_BRIGHTNESS in kwargs:
self._api(self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS])) self.hass.async_add_job(self._api(
self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS], **keys)))
else: else:
self._api(self._group.set_state(1)) self.hass.async_add_job(self._api(self._group.set_state(1)))
@callback
def _async_start_observe(self, exc=None):
"""Start observation of light."""
from pytradfri.error import PyTradFriError
if exc:
_LOGGER.warning("Observation failed for %s", self._name,
exc_info=exc)
def update(self):
"""Fetch new state data for this group."""
from pytradfri import RequestTimeout
try: try:
self._api(self._group.update()) cmd = self._group.observe(callback=self._observe_update,
except RequestTimeout: err_callback=self._async_start_observe,
_LOGGER.warning("Tradfri update request timed out") duration=0)
self.hass.async_add_job(self._api(cmd))
except PyTradFriError as err:
_LOGGER.warning("Observation failed, trying again", exc_info=err)
self._async_start_observe()
def _refresh(self, group):
"""Refresh the light data."""
self._group = group
self._name = group.name
def _observe_update(self, tradfri_device):
"""Receive new state data for this light."""
self._refresh(tradfri_device)
self.hass.async_add_job(self.async_update_ha_state())
class Tradfri(Light): class TradfriLight(Light):
"""The platform class required by Home Asisstant.""" """The platform class required by Home Assistant."""
def __init__(self, light, api): def __init__(self, light, api):
"""Initialize a Light.""" """Initialize a Light."""
self._light = api(light)
self._api = api self._api = api
self._light = None
# Caching of LightControl and light object self._light_control = None
self._light_control = self._light.light_control self._light_data = None
self._light_data = self._light_control.lights[0] self._name = None
self._name = self._light.name
self._rgb_color = None self._rgb_color = None
self._features = SUPPORT_BRIGHTNESS self._features = SUPPORTED_FEATURES
self._temp_supported = False
if self._light_data.hex_color is not None: self._refresh(light)
if self._light.device_info.manufacturer == IKEA:
self._features |= SUPPORT_COLOR_TEMP
else:
self._features |= SUPPORT_RGB_COLOR
self._ok_temps = \
self._light.device_info.manufacturer in ALLOWED_TEMPERATURES
@property @property
def min_mireds(self): def min_mireds(self):
@ -126,6 +170,30 @@ class Tradfri(Light):
from pytradfri.color import MIN_KELVIN_WS from pytradfri.color import MIN_KELVIN_WS
return color_util.color_temperature_kelvin_to_mired(MIN_KELVIN_WS) return color_util.color_temperature_kelvin_to_mired(MIN_KELVIN_WS)
@property
def device_state_attributes(self):
"""Return the devices' state attributes."""
info = self._light.device_info
attrs = {
'manufacturer': info.manufacturer,
'model_number': info.model_number,
'serial': info.serial,
'firmware_version': info.firmware_version,
'power_source': info.power_source_str,
'battery_level': info.battery_level
}
return attrs
@asyncio.coroutine
def async_added_to_hass(self):
"""Start thread when added to hass."""
self._async_start_observe()
@property
def should_poll(self):
"""No polling needed for tradfri light."""
return False
@property @property
def supported_features(self): def supported_features(self):
"""Flag supported features.""" """Flag supported features."""
@ -151,7 +219,7 @@ class Tradfri(Light):
"""Return the CT color value in mireds.""" """Return the CT color value in mireds."""
if (self._light_data.kelvin_color is None or if (self._light_data.kelvin_color is None or
self.supported_features & SUPPORT_COLOR_TEMP == 0 or self.supported_features & SUPPORT_COLOR_TEMP == 0 or
not self._ok_temps): not self._temp_supported):
return None return None
return color_util.color_temperature_kelvin_to_mired( return color_util.color_temperature_kelvin_to_mired(
self._light_data.kelvin_color self._light_data.kelvin_color
@ -162,42 +230,90 @@ class Tradfri(Light):
"""RGB color of the light.""" """RGB color of the light."""
return self._rgb_color return self._rgb_color
def turn_off(self, **kwargs): @asyncio.coroutine
def async_turn_off(self, **kwargs):
"""Instruct the light to turn off.""" """Instruct the light to turn off."""
self._api(self._light_control.set_state(False)) self.hass.async_add_job(self._api(
self._light_control.set_state(False)))
def turn_on(self, **kwargs): @asyncio.coroutine
def async_turn_on(self, **kwargs):
""" """
Instruct the light to turn on. Instruct the light to turn on.
After adding "self._light_data.hexcolor is not None" After adding "self._light_data.hexcolor is not None"
for ATTR_RGB_COLOR, this also supports Philips Hue bulbs. for ATTR_RGB_COLOR, this also supports Philips Hue bulbs.
""" """
if ATTR_BRIGHTNESS in kwargs:
self._api(self._light_control.set_dimmer(kwargs[ATTR_BRIGHTNESS]))
else:
self._api(self._light_control.set_state(True))
if ATTR_RGB_COLOR in kwargs and self._light_data.hex_color is not None: if ATTR_RGB_COLOR in kwargs and self._light_data.hex_color is not None:
self._api(self._light.light_control.set_rgb_color( self.hass.async_add_job(self._api(
*kwargs[ATTR_RGB_COLOR])) self._light.light_control.set_rgb_color(
*kwargs[ATTR_RGB_COLOR])))
elif ATTR_COLOR_TEMP in kwargs and \ elif ATTR_COLOR_TEMP in kwargs and \
self._light_data.hex_color is not None and self._ok_temps: self._light_data.hex_color is not None and \
self._temp_supported:
kelvin = color_util.color_temperature_mired_to_kelvin( kelvin = color_util.color_temperature_mired_to_kelvin(
kwargs[ATTR_COLOR_TEMP]) kwargs[ATTR_COLOR_TEMP])
self._api(self._light_control.set_kelvin_color(kelvin)) self.hass.async_add_job(self._api(
self._light_control.set_kelvin_color(kelvin)))
keys = {}
if ATTR_TRANSITION in kwargs:
keys['transition_time'] = int(kwargs[ATTR_TRANSITION])
if ATTR_BRIGHTNESS in kwargs:
self.hass.async_add_job(self._api(
self._light_control.set_dimmer(kwargs[ATTR_BRIGHTNESS],
**keys)))
else:
self.hass.async_add_job(self._api(
self._light_control.set_state(True)))
@callback
def _async_start_observe(self, exc=None):
"""Start observation of light."""
from pytradfri.error import PyTradFriError
if exc:
_LOGGER.warning("Observation failed for %s", self._name,
exc_info=exc)
def update(self):
"""Fetch new state data for this light."""
from pytradfri import RequestTimeout
try: try:
self._api(self._light.update()) cmd = self._light.observe(callback=self._observe_update,
except RequestTimeout as exception: err_callback=self._async_start_observe,
_LOGGER.warning("Tradfri update request timed out: %s", exception) duration=0)
self.hass.async_add_job(self._api(cmd))
except PyTradFriError as err:
_LOGGER.warning("Observation failed, trying again", exc_info=err)
self._async_start_observe()
def _refresh(self, light):
"""Refresh the light data."""
self._light = light
# Caching of LightControl and light object
self._light_control = light.light_control
self._light_data = light.light_control.lights[0]
self._name = light.name
self._rgb_color = None
self._features = SUPPORTED_FEATURES
if self._light_data.hex_color is not None:
if self._light.device_info.manufacturer == IKEA:
self._features |= SUPPORT_COLOR_TEMP
else:
self._features |= SUPPORT_RGB_COLOR
self._temp_supported = self._light.device_info.manufacturer \
in ALLOWED_TEMPERATURES
def _observe_update(self, tradfri_device):
"""Receive new state data for this light."""
self._refresh(tradfri_device)
# Handle Hue lights paired with the gateway # Handle Hue lights paired with the gateway
# hex_color is 0 when bulb is unreachable # hex_color is 0 when bulb is unreachable
if self._light_data.hex_color not in (None, '0'): if self._light_data.hex_color not in (None, '0'):
self._rgb_color = color_util.rgb_hex_to_rgb_list( self._rgb_color = color_util.rgb_hex_to_rgb_list(
self._light_data.hex_color) self._light_data.hex_color)
self.hass.async_add_job(self.async_update_ha_state())

View File

@ -0,0 +1,116 @@
"""
Support for the IKEA Tradfri platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.tradfri/
"""
import asyncio
import logging
from datetime import timedelta
from homeassistant.core import callback
from homeassistant.components.tradfri import KEY_GATEWAY, KEY_API
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['tradfri']
SCAN_INTERVAL = timedelta(minutes=5)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the IKEA Tradfri device platform."""
if discovery_info is None:
return
gateway_id = discovery_info['gateway']
api = hass.data[KEY_API][gateway_id]
gateway = hass.data[KEY_GATEWAY][gateway_id]
devices_command = gateway.get_devices()
devices_commands = yield from api(devices_command)
all_devices = yield from api(*devices_commands)
devices = [dev for dev in all_devices if not dev.has_light_control]
async_add_devices(TradfriDevice(device, api) for device in devices)
class TradfriDevice(Entity):
"""The platform class required by Home Assistant."""
def __init__(self, device, api):
"""Initialize the device."""
self._api = api
self._device = None
self._name = None
self._refresh(device)
@asyncio.coroutine
def async_added_to_hass(self):
"""Start thread when added to hass."""
self._async_start_observe()
@property
def should_poll(self):
"""No polling needed for tradfri."""
return False
@property
def name(self):
"""Return the display name of this device."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit_of_measurement of the device."""
return '%'
@property
def device_state_attributes(self):
"""Return the devices' state attributes."""
info = self._device.device_info
attrs = {
'manufacturer': info.manufacturer,
'model_number': info.model_number,
'serial': info.serial,
'firmware_version': info.firmware_version,
'power_source': info.power_source_str,
'battery_level': info.battery_level
}
return attrs
@property
def state(self):
"""Return the current state of the device."""
return self._device.device_info.battery_level
@callback
def _async_start_observe(self, exc=None):
"""Start observation of light."""
from pytradfri.error import PyTradFriError
if exc:
_LOGGER.warning("Observation failed for %s", self._name,
exc_info=exc)
try:
cmd = self._device.observe(callback=self._observe_update,
err_callback=self._async_start_observe,
duration=0)
self.hass.async_add_job(self._api(cmd))
except PyTradFriError as err:
_LOGGER.warning("Observation failed, trying again", exc_info=err)
self._async_start_observe()
def _refresh(self, device):
"""Refresh the device data."""
self._device = device
self._name = device.name
def _observe_update(self, tradfri_device):
"""Receive new state data for this device."""
self._refresh(tradfri_device)
self.hass.async_add_job(self.async_update_ha_state())

View File

@ -16,7 +16,7 @@ from homeassistant.helpers import discovery
from homeassistant.const import CONF_HOST, CONF_API_KEY from homeassistant.const import CONF_HOST, CONF_API_KEY
from homeassistant.components.discovery import SERVICE_IKEA_TRADFRI from homeassistant.components.discovery import SERVICE_IKEA_TRADFRI
REQUIREMENTS = ['pytradfri==2.2'] REQUIREMENTS = ['pytradfri==2.2.2']
DOMAIN = 'tradfri' DOMAIN = 'tradfri'
CONFIG_FILE = 'tradfri.conf' CONFIG_FILE = 'tradfri.conf'
@ -111,16 +111,21 @@ def async_setup(hass, config):
def _setup_gateway(hass, hass_config, host, key, allow_tradfri_groups): def _setup_gateway(hass, hass_config, host, key, allow_tradfri_groups):
"""Create a gateway.""" """Create a gateway."""
from pytradfri import Gateway, RequestError from pytradfri import Gateway, RequestError
from pytradfri.api.libcoap_api import api_factory try:
from pytradfri.api.aiocoap_api import api_factory
except ImportError:
_LOGGER.exception("Looks like something isn't installed!")
return False
try: try:
api = api_factory(host, key) api = yield from api_factory(host, key, loop=hass.loop)
except RequestError: except RequestError:
_LOGGER.exception("Tradfri setup failed.")
return False return False
gateway = Gateway() gateway = Gateway()
# pylint: disable=no-member gateway_info_result = yield from api(gateway.get_gateway_info())
gateway_id = api(gateway.get_gateway_info()).id gateway_id = gateway_info_result.id
hass.data.setdefault(KEY_API, {}) hass.data.setdefault(KEY_API, {})
hass.data.setdefault(KEY_GATEWAY, {}) hass.data.setdefault(KEY_GATEWAY, {})
gateways = hass.data[KEY_GATEWAY] gateways = hass.data[KEY_GATEWAY]
@ -137,6 +142,8 @@ def _setup_gateway(hass, hass_config, host, key, allow_tradfri_groups):
gateways[gateway_id] = gateway gateways[gateway_id] = gateway
hass.async_add_job(discovery.async_load_platform( hass.async_add_job(discovery.async_load_platform(
hass, 'light', DOMAIN, {'gateway': gateway_id}, hass_config)) hass, 'light', DOMAIN, {'gateway': gateway_id}, hass_config))
hass.async_add_job(discovery.async_load_platform(
hass, 'sensor', DOMAIN, {'gateway': gateway_id}, hass_config))
return True return True

View File

@ -829,7 +829,7 @@ pythonegardia==1.0.21
pytrackr==0.0.5 pytrackr==0.0.5
# homeassistant.components.tradfri # homeassistant.components.tradfri
pytradfri==2.2 pytradfri==2.2.2
# homeassistant.components.device_tracker.unifi # homeassistant.components.device_tracker.unifi
pyunifi==2.13 pyunifi==2.13

View File

@ -11,7 +11,7 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
#ENV INSTALL_FFMPEG no #ENV INSTALL_FFMPEG no
#ENV INSTALL_LIBCEC no #ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no #ENV INSTALL_PHANTOMJS no
#ENV INSTALL_COAP_CLIENT no #ENV INSTALL_COAP no
#ENV INSTALL_SSOCR no #ENV INSTALL_SSOCR no
VOLUME /config VOLUME /config

View File

@ -0,0 +1,23 @@
#!/bin/sh
# Installs a modified coap client with support for dtls for use with IKEA Tradfri
# Stop on errors
set -e
python3 -m pip install cython
cd /usr/src/app/
mkdir -p build && cd build
git clone --depth 1 https://git.fslab.de/jkonra2m/tinydtls
cd tinydtls
autoreconf
./configure --with-ecc --without-debug
cd cython
python3 setup.py install
cd ../..
git clone --depth 1 https://github.com/chrysn/aiocoap/
cd aiocoap
git reset --hard 0df6a1e44582de99ae944b6a7536d08e2a612e8f
python3 -m pip install .

View File

@ -1,17 +0,0 @@
#!/bin/sh
# Installs a modified coap client with support for dtls for use with IKEA Tradfri
# Stop on errors
set -e
apt-get install -y --no-install-recommends git autoconf automake libtool
cd /usr/src/app/
mkdir -p build && cd build
git clone --depth 1 --recursive -b dtls https://github.com/home-assistant/libcoap.git
cd libcoap
./autogen.sh
./configure --disable-documentation --disable-shared --without-debug CFLAGS="-D COAP_DEBUG_FD=stderr"
make
make install

View File

@ -9,7 +9,7 @@ INSTALL_OPENALPR="${INSTALL_OPENALPR:-yes}"
INSTALL_FFMPEG="${INSTALL_FFMPEG:-yes}" INSTALL_FFMPEG="${INSTALL_FFMPEG:-yes}"
INSTALL_LIBCEC="${INSTALL_LIBCEC:-yes}" INSTALL_LIBCEC="${INSTALL_LIBCEC:-yes}"
INSTALL_PHANTOMJS="${INSTALL_PHANTOMJS:-yes}" INSTALL_PHANTOMJS="${INSTALL_PHANTOMJS:-yes}"
INSTALL_COAP_CLIENT="${INSTALL_COAP_CLIENT:-yes}" INSTALL_COAP="${INSTALL_COAP:-yes}"
INSTALL_SSOCR="${INSTALL_SSOCR:-yes}" INSTALL_SSOCR="${INSTALL_SSOCR:-yes}"
# Required debian packages for running hass or components # Required debian packages for running hass or components
@ -59,8 +59,8 @@ if [ "$INSTALL_PHANTOMJS" == "yes" ]; then
virtualization/Docker/scripts/phantomjs virtualization/Docker/scripts/phantomjs
fi fi
if [ "$INSTALL_COAP_CLIENT" == "yes" ]; then if [ "$INSTALL_COAP" == "yes" ]; then
virtualization/Docker/scripts/coap_client virtualization/Docker/scripts/aiocoap
fi fi
if [ "$INSTALL_SSOCR" == "yes" ]; then if [ "$INSTALL_SSOCR" == "yes" ]; then