From 3f0fa3eab905ace28f54749a790a9d5d4ed5705f Mon Sep 17 00:00:00 2001 From: riley206 Date: Tue, 13 Feb 2024 08:36:48 -0800 Subject: [PATCH 1/3] ssl verification --- .../interfaces/home_assistant.py | 233 ++++++++++-------- 1 file changed, 129 insertions(+), 104 deletions(-) diff --git a/services/core/PlatformDriverAgent/platform_driver/interfaces/home_assistant.py b/services/core/PlatformDriverAgent/platform_driver/interfaces/home_assistant.py index 6bfbafab10..c06d225548 100644 --- a/services/core/PlatformDriverAgent/platform_driver/interfaces/home_assistant.py +++ b/services/core/PlatformDriverAgent/platform_driver/interfaces/home_assistant.py @@ -1,39 +1,25 @@ # -*- coding: utf-8 -*- {{{ -# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# ===----------------------------------------------------------------------=== # -# Copyright 2020, Battelle Memorial Institute. +# Component of Eclipse VOLTTRON # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# ===----------------------------------------------------------------------=== # -# http://www.apache.org/licenses/LICENSE-2.0 +# Copyright 2024 Battelle Memorial Institute # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 # -# This material was prepared as an account of work sponsored by an agency of -# the United States Government. Neither the United States Government nor the -# United States Department of Energy, nor Battelle, nor any of their -# employees, nor any jurisdiction or organization that has cooperated in the -# development of these materials, makes any warranty, express or -# implied, or assumes any legal liability or responsibility for the accuracy, -# completeness, or usefulness or any information, apparatus, product, -# software, or process disclosed, or represents that its use would not infringe -# privately owned rights. Reference herein to any specific commercial product, -# process, or service by trade name, trademark, manufacturer, or otherwise -# does not necessarily constitute or imply its endorsement, recommendation, or -# favoring by the United States Government or any agency thereof, or -# Battelle Memorial Institute. The views and opinions of authors expressed -# herein do not necessarily state or reflect those of the -# United States Government or any agency thereof. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. # -# PACIFIC NORTHWEST NATIONAL LABORATORY operated by -# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY -# under Contract DE-AC05-76RL01830 +# ===----------------------------------------------------------------------=== # }}} @@ -42,23 +28,27 @@ import json import sys from platform_driver.interfaces import BaseInterface, BaseRegister, BasicRevert -from volttron.platform.agent import utils # added this to pull from config store +from volttron.platform.agent import utils from volttron.platform.vip.agent import Agent import logging import requests from requests import get _log = logging.getLogger(__name__) -type_mapping = {"string": str, - "int": int, - "integer": int, - "float": float, - "bool": bool, - "boolean": bool} +type_mapping = {"string": str, "int": int, "integer": int, "float": float, "bool": bool, "boolean": bool} class HomeAssistantRegister(BaseRegister): - def __init__(self, read_only, pointName, units, reg_type, attributes, entity_id, entity_point, default_value=None, + + def __init__(self, + read_only, + pointName, + units, + reg_type, + attributes, + entity_id, + entity_point, + default_value=None, description=''): super(HomeAssistantRegister, self).__init__("byte", read_only, pointName, units, description='') self.reg_type = reg_type @@ -71,7 +61,7 @@ def __init__(self, read_only, pointName, units, reg_type, attributes, entity_id, def _post_method(url, headers, data, operation_description): err = None try: - response = requests.post(url, headers=headers, json=data) + response = requests.post(url, headers=headers, json=data, verify=self.verify_option) if response.status_code == 200: _log.info(f"Success: {operation_description}") else: @@ -86,32 +76,42 @@ def _post_method(url, headers, data, operation_description): class Interface(BasicRevert, BaseInterface): + def __init__(self, **kwargs): super(Interface, self).__init__(**kwargs) self.point_name = None - self.ip_address = None + self.url = None self.access_token = None - self.port = None + self.verify_ssl = True # Default to True for security self.units = None - - def configure(self, config_dict, registry_config_str): # grabbing from config - self.ip_address = config_dict.get("ip_address", None) + + def configure(self, config_dict, registry_config_str): + self.url = config_dict.get("url", None) self.access_token = config_dict.get("access_token", None) - self.port = config_dict.get("port", None) - + self.verify_ssl = config_dict.get("verify_ssl", False) + self.ssl_cert_path = config_dict.get("ssl_cert_path", "") + # Check for None values - if self.ip_address is None: - _log.error("IP address is not set.") - raise ValueError("IP address is required.") + if self.url is None: + _log.error("URL address is not set.") + raise ValueError("URL is required.") if self.access_token is None: _log.error("Access token is not set.") raise ValueError("Access token is required.") - if self.port is None: - _log.error("Port is not set.") - raise ValueError("Port is required.") - - self.parse_config(registry_config_str) - + + if not self.verify_ssl: + import urllib3 + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + if self.ssl_cert_path: + self.verify_option = self.ssl_cert_path + else: + self.verify_option = self.verify_ssl + + _log.info(f"using verify option: {self.verify_option}") + + self.parse_config(registry_config_str) + def get_point(self, point_name): register = self.get_register_by_name(point_name) @@ -126,11 +126,10 @@ def get_point(self, point_name): def _set_point(self, point_name, value): register = self.get_register_by_name(point_name) if register.read_only: - raise IOError( - "Trying to write to a point configured read only: " + point_name) - register.value = register.reg_type(value) # setting the value + raise IOError("Trying to write to a point configured read only: " + point_name) + register.value = register.reg_type(value) # setting the value entity_point = register.entity_point - # Changing lights values in home assistant based off of register value. + # Changing lights values in home assistant based off of register value. if "light." in register.entity_id: if entity_point == "state": if isinstance(register.value, int) and register.value in [0, 1]: @@ -142,9 +141,10 @@ def _set_point(self, point_name, value): error_msg = f"State value for {register.entity_id} should be an integer value of 1 or 0" _log.info(error_msg) raise ValueError(error_msg) - + elif entity_point == "brightness": - if isinstance(register.value, int) and 0 <= register.value <= 255: # Make sure its int and within range + if isinstance(register.value, + int) and 0 <= register.value <= 255: # Make sure its int and within range self.change_brightness(register.entity_id, register.value) else: error_msg = "Brightness value should be an integer between 0 and 255" @@ -154,8 +154,22 @@ def _set_point(self, point_name, value): error_msg = f"Unexpected point_name {point_name} for register {register.entity_id}" _log.error(error_msg) raise ValueError(error_msg) - - # Changing thermostat values. + + elif "input_boolean." in register.entity_id: + if entity_point == "state": + if isinstance(register.value, int) and register.value in [0, 1]: + if register.value == 1: + self.set_input_boolean(register.entity_id, "on") + elif register.value == 0: + self.set_input_boolean(register.entity_id, "off") + else: + error_msg = f"State value for {register.entity_id} should be an integer value of 1 or 0" + _log.info(error_msg) + raise ValueError(error_msg) + else: + _log.info(f"Currently, input_booleans only support state") + + # Changing thermostat values. elif "climate." in register.entity_id: if entity_point == "state": if isinstance(register.value, int) and register.value in [0, 2, 3, 4]: @@ -184,23 +198,23 @@ def _set_point(self, point_name, value): _log.error(error_msg) raise ValueError(error_msg) return register.value - + def get_entity_data(self, point_name): headers = { "Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json", } # the /states grabs current state AND attributes of a specific entity - url = f"http://{self.ip_address}:{self.port}/api/states/{point_name}" - response = requests.get(url, headers=headers) + url = f"{self.url}/api/states/{point_name}" + response = requests.get(url, headers=headers, verify=self.verify_option) if response.status_code == 200: - return response.json() # return the json attributes from entity + return response.json() # return the json attributes from entity else: error_msg = f"Request failed with status code {response.status_code}, Point name: {point_name}, " \ f"response: {response.text}" _log.error(error_msg) raise Exception(error_msg) - + def _scrape_all(self): result = {} read_registers = self.get_registers_by_type("byte", True) @@ -210,11 +224,10 @@ def _scrape_all(self): entity_id = register.entity_id entity_point = register.entity_point try: - entity_data = self.get_entity_data(entity_id) # Using Entity ID to get data - if "climate." in entity_id: # handling thermostats. + entity_data = self.get_entity_data(entity_id) # Using Entity ID to get data + if "climate." in entity_id: # handling thermostats. if entity_point == "state": state = entity_data.get("state", None) - # Giving thermostat states an equivalent number. if state == "off": register.value = 0 @@ -238,10 +251,10 @@ def _scrape_all(self): register.value = attribute result[register.point_name] = attribute # handling light states - elif "light." in entity_id: + elif "light." or "input_boolean." in entity_id: # Checks for lights or input bools since they have the same states. if entity_point == "state": state = entity_data.get("state", None) - # Converting light states to numbers. + # Converting light states to numbers. if state == "on": register.value = 1 result[register.point_name] = 1 @@ -252,9 +265,9 @@ def _scrape_all(self): attribute = entity_data.get("attributes", {}).get(f"{entity_point}", 0) register.value = attribute result[register.point_name] = attribute - else: # handling all devices that are not thermostats or light states + else: # handling all devices that are not thermostats or light states if entity_point == "state": - + state = entity_data.get("state", None) register.value = state result[register.point_name] = state @@ -283,25 +296,21 @@ def parse_config(self, config_dict): self.point_name = regDef['Volttron Point Name'] self.units = regDef['Units'] description = regDef.get('Notes', '') - - default_value = str(regDef.get("Starting Value", 'sin')).strip() - if not default_value: - default_value = None + default_value = ("Starting Value") type_name = regDef.get("Type", 'string') reg_type = type_mapping.get(type_name, str) attributes = regDef.get('Attributes', {}) register_type = HomeAssistantRegister - register = register_type( - read_only, - self.point_name, - self.units, - reg_type, - attributes, - entity_id, - entity_point, - default_value=default_value, - description=description) + register = register_type(read_only, + self.point_name, + self.units, + reg_type, + attributes, + entity_id, + entity_point, + default_value=default_value, + description=description) if default_value is not None: self.set_default(self.point_name, register.value) @@ -309,7 +318,7 @@ def parse_config(self, config_dict): self.insert_register(register) def turn_off_lights(self, entity_id): - url = f"http://{self.ip_address}:{self.port}/api/services/light/turn_off" + url = f"{self.url}/api/services/light/turn_off" headers = { "Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json", @@ -320,15 +329,13 @@ def turn_off_lights(self, entity_id): _post_method(url, headers, payload, f"turn off {entity_id}") def turn_on_lights(self, entity_id): - url = f"http://{self.ip_address}:{self.port}/api/services/light/turn_on" + url = f"{self.url}/api/services/light/turn_on" headers = { - "Authorization": f"Bearer {self.access_token}", - "Content-Type": "application/json", + "Authorization": f"Bearer {self.access_token}", + "Content-Type": "application/json", } - payload = { - "entity_id": f"{entity_id}" - } + payload = {"entity_id": f"{entity_id}"} _post_method(url, headers, payload, f"turn on {entity_id}") def change_thermostat_mode(self, entity_id, mode): @@ -337,10 +344,10 @@ def change_thermostat_mode(self, entity_id, mode): _log.error(f"{entity_id} is not a valid thermostat entity ID.") return # Build header - url = f"http://{self.ip_address}:{self.port}/api/services/climate/set_hvac_mode" + url = f"{self.url}/api/services/climate/set_hvac_mode" headers = { - "Authorization": f"Bearer {self.access_token}", - "content-type": "application/json", + "Authorization": f"Bearer {self.access_token}", + "content-type": "application/json", } # Build data data = { @@ -356,14 +363,14 @@ def set_thermostat_temperature(self, entity_id, temperature): _log.error(f"{entity_id} is not a valid thermostat entity ID.") return - url = f"http://{self.ip_address}:{self.port}/api/services/climate/set_temperature" + url = f"{self.url}/api/services/climate/set_temperature" headers = { "Authorization": f"Bearer {self.access_token}", "content-type": "application/json", } - + if self.units == "C": - converted_temp = round((temperature - 32) * 5/9, 1) + converted_temp = round((temperature - 32) * 5 / 9, 1) _log.info(f"Converted temperature {converted_temp}") data = { "entity_id": entity_id, @@ -377,10 +384,10 @@ def set_thermostat_temperature(self, entity_id, temperature): _post_method(url, headers, data, f"set temperature of {entity_id} to {temperature}") def change_brightness(self, entity_id, value): - url = f"http://{self.ip_address}:{self.port}/api/services/light/turn_on" + url = f"{self.url}/api/services/light/turn_on" headers = { - "Authorization": f"Bearer {self.access_token}", - "Content-Type": "application/json", + "Authorization": f"Bearer {self.access_token}", + "Content-Type": "application/json", } # ranges from 0 - 255 payload = { @@ -389,3 +396,21 @@ def change_brightness(self, entity_id, value): } _post_method(url, headers, payload, f"set brightness of {entity_id} to {value}") + + def set_input_boolean(self, entity_id, state): + service = 'turn_on' if state == 'on' else 'turn_off' + url = f"{self.url}/api/services/input_boolean/{service}" + headers = { + "Authorization": f"Bearer {self.access_token}", + "Content-Type": "application/json", + } + + payload = {"entity_id": entity_id} + + response = requests.post(url, headers=headers, json=payload, verify=self.verify_option) + + # Optionally check for a successful response + if response.status_code == 200: + print(f"Successfully set {entity_id} to {state}") + else: + print(f"Failed to set {entity_id} to {state}: {response.text}") \ No newline at end of file From 192ff5365a25c0d9e949e1b0d8ba3e38e790edba Mon Sep 17 00:00:00 2001 From: riley206 Date: Tue, 13 Feb 2024 08:50:19 -0800 Subject: [PATCH 2/3] updated test to reflect new changes --- .../tests/test_home_assistant.py | 239 ++++++++++-------- 1 file changed, 127 insertions(+), 112 deletions(-) diff --git a/services/core/PlatformDriverAgent/tests/test_home_assistant.py b/services/core/PlatformDriverAgent/tests/test_home_assistant.py index b926db549b..23173154f6 100644 --- a/services/core/PlatformDriverAgent/tests/test_home_assistant.py +++ b/services/core/PlatformDriverAgent/tests/test_home_assistant.py @@ -1,40 +1,27 @@ # -*- coding: utf-8 -*- {{{ -# vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et: +# ===----------------------------------------------------------------------=== # -# Copyright 2020, Battelle Memorial Institute. +# Component of Eclipse VOLTTRON # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# ===----------------------------------------------------------------------=== # -# http://www.apache.org/licenses/LICENSE-2.0 +# Copyright 2024 Battelle Memorial Institute # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 # -# This material was prepared as an account of work sponsored by an agency of -# the United States Government. Neither the United States Government nor the -# United States Department of Energy, nor Battelle, nor any of their -# employees, nor any jurisdiction or organization that has cooperated in the -# development of these materials, makes any warranty, express or -# implied, or assumes any legal liability or responsibility for the accuracy, -# completeness, or usefulness or any information, apparatus, product, -# software, or process disclosed, or represents that its use would not infringe -# privately owned rights. Reference herein to any specific commercial product, -# process, or service by trade name, trademark, manufacturer, or otherwise -# does not necessarily constitute or imply its endorsement, recommendation, or -# favoring by the United States Government or any agency thereof, or -# Battelle Memorial Institute. The views and opinions of authors expressed -# herein do not necessarily state or reflect those of the -# United States Government or any agency thereof. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. # -# PACIFIC NORTHWEST NATIONAL LABORATORY operated by -# BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY -# under Contract DE-AC05-76RL01830 +# ===----------------------------------------------------------------------=== # }}} + import json import logging import pytest @@ -52,60 +39,108 @@ utils.setup_logging() logger = logging.getLogger(__name__) -HOMEASSISTANT_DEVICE_TOPIC = "devices/home_assistant" -HOMEASSISTANT_TEST_IP = "" +# To run these tests, create a helper toggle named volttrontest in your Home Assistant instance. +# This can be done by going to Settings > Devices & services > Helpers > Create Helper > Toggle +HOMEASSISTANT_URL = "" # Example, http://0.0.0.0:8123 ACCESS_TOKEN = "" -PORT = "" +SSL_CERT_PATH = "" # Optional for self signed cert +VERIFY_SSL = True -skip_msg = "Some configuration variables are not set. Check HOMEASSISTANT_TEST_IP, ACCESS_TOKEN, and PORT" -# Skip tests if variables are not set -pytestmark = pytest.mark.skipif( - not (HOMEASSISTANT_TEST_IP and ACCESS_TOKEN and PORT), - reason=skip_msg -) -HOMEASSISTANT_DEVICE_TOPIC = "devices/home_assistant" +def test_scrape_all(publish_agent): + # add Home Assistant Driver to Platform Driver + registry_obj = [{ + "Entity ID": "input_boolean.volttrontest", + "Entity Point": "state", + "Volttron Point Name": "bool_state", + "Units": "On / Off", + "Units Details": "off: 0, on: 1", + "Writable": True, + "Starting Value": 3, + "Type": "int", + "Notes": "lights hallway" + }] + publish_agent.vip.rpc.call(CONFIGURATION_STORE, + "manage_store", + PLATFORM_DRIVER, + "homeassistant_test.json", + json.dumps(registry_obj), + config_type="json") + driver_config = { + "driver_config": { + "url": HOMEASSISTANT_URL, + "access_token": ACCESS_TOKEN, + "verify_ssl": VERIFY_SSL, + "ssl_cert_path": SSL_CERT_PATH + }, + "driver_type": "home_assistant", + "registry_config": f"config://homeassistant_test.json", + "timezone": "US/Pacific", + "interval": 30, + } + publish_agent.vip.rpc.call(CONFIGURATION_STORE, + "manage_store", + PLATFORM_DRIVER, + "devices/home_assistant", + jsonapi.dumps(driver_config), + config_type='json') -# Get the point which will should be off -def test_get_point(volttron_instance, config_store): - expected_values = 0 - agent = volttron_instance.dynamic_agent - result = agent.vip.rpc.call(PLATFORM_DRIVER, 'get_point', 'home_assistant', 'light_state').get(timeout=20) - assert result == expected_values, "The result does not match the expected result." + gevent.sleep(10) + actual_scrape_all_results = publish_agent.vip.rpc.call(PLATFORM_DRIVER, "scrape_all", + "home_assistant").get(timeout=10) + expected_scrape_all_results = {'bool_state': 0} + assert actual_scrape_all_results == expected_scrape_all_results -# The default value for this fake light is 3. If the test cannot reach out to home assistant, -# the value will default to 3 meking the test fail. -def test_data_poll(volttron_instance: PlatformWrapper, config_store): - expected_values = [{'light_state': 0}, {'light_state': 1}] - agent = volttron_instance.dynamic_agent - result = agent.vip.rpc.call(PLATFORM_DRIVER, 'scrape_all', 'home_assistant').get(timeout=20) - assert result in expected_values, "The result does not match the expected result." +def test_get_point_set_point(publish_agent): + actual_boolValue = publish_agent.vip.rpc.call(PLATFORM_DRIVER, "get_point", "home_assistant", + "bool_state").get(timeout=10) + assert actual_boolValue == 0 -# Turn on the light. Light is automatically turned off every 30 seconds to allow test to turn -# it on and receive the correct value. -def test_set_point(volttron_instance, config_store): - expected_values = {'light_state': 1} - agent = volttron_instance.dynamic_agent - agent.vip.rpc.call(PLATFORM_DRIVER, 'set_point', 'home_assistant', 'light_state', 1) - gevent.sleep(10) - result = agent.vip.rpc.call(PLATFORM_DRIVER, 'scrape_all', 'home_assistant').get(timeout=20) - assert result == expected_values, "The result does not match the expected result." + #set_point + actual_boolValue = publish_agent.vip.rpc.call(PLATFORM_DRIVER, "set_point", "home_assistant", "bool_state", + 1).get(timeout=10) + assert actual_boolValue == 1 @pytest.fixture(scope="module") -def config_store(volttron_instance, platform_driver): - - capabilities = [{"edit_config_store": {"identity": PLATFORM_DRIVER}}] - volttron_instance.add_capabilities(volttron_instance.dynamic_agent.core.publickey, capabilities) - - registry_config = "homeassistant_test.json" +def publish_agent(volttron_instance: PlatformWrapper): + assert volttron_instance.is_running() + vi = volttron_instance + assert vi is not None + assert vi.is_running() + + # install platform driver + config = { + "driver_scrape_interval": 0.05, + "publish_breadth_first_all": "false", + "publish_depth_first": "false", + "publish_breadth_first": "false" + } + puid = vi.install_agent(agent_dir="volttron-platform-driver", + config_file=config, + start=False, + vip_identity=PLATFORM_DRIVER) + assert puid is not None + gevent.sleep(1) + assert vi.start_agent(puid) + assert vi.is_agent_running(puid) + + # create the publish agent + publish_agent = volttron_instance.build_agent() + assert publish_agent.core.identity + gevent.sleep(1) + + capabilities = {"edit_config_store": {"identity": PLATFORM_DRIVER}} + volttron_instance.add_capabilities(publish_agent.core.publickey, capabilities) + + # Add Home Assistant Driver to Platform Driver registry_obj = [{ - "Entity ID": "light.fake_light", + "Entity ID": "input_boolean.volttrontest", "Entity Point": "state", - "Volttron Point Name": "light_state", + "Volttron Point Name": "bool_state", "Units": "On / Off", "Units Details": "off: 0, on: 1", "Writable": True, @@ -113,55 +148,35 @@ def config_store(volttron_instance, platform_driver): "Type": "int", "Notes": "lights hallway" }] + publish_agent.vip.rpc.call(CONFIGURATION_STORE, + "manage_store", + PLATFORM_DRIVER, + "homeassistant_test.json", + json.dumps(registry_obj), + config_type="json") - volttron_instance.dynamic_agent.vip.rpc.call(CONFIGURATION_STORE, - "manage_store", - PLATFORM_DRIVER, - registry_config, - json.dumps(registry_obj), - config_type="json") - gevent.sleep(2) - # driver config driver_config = { - "driver_config": {"ip_address": HOMEASSISTANT_TEST_IP, "access_token": ACCESS_TOKEN, "port": PORT}, + "driver_config": { + "url": HOMEASSISTANT_URL, + "access_token": ACCESS_TOKEN, + "verify_ssl": VERIFY_SSL, + "ssl_cert_path": SSL_CERT_PATH + }, "driver_type": "home_assistant", - "registry_config": f"config://{registry_config}", + "registry_config": f"config://homeassistant_test.json", "timezone": "US/Pacific", "interval": 30, } + publish_agent.vip.rpc.call(CONFIGURATION_STORE, + "manage_store", + PLATFORM_DRIVER, + "devices/home_assistant", + jsonapi.dumps(driver_config), + config_type='json') - volttron_instance.dynamic_agent.vip.rpc.call(CONFIGURATION_STORE, - "manage_store", - PLATFORM_DRIVER, - HOMEASSISTANT_DEVICE_TOPIC, - json.dumps(driver_config), - config_type="json" - ) - gevent.sleep(2) - - yield platform_driver - - print("Wiping out store.") - volttron_instance.dynamic_agent.vip.rpc.call(CONFIGURATION_STORE, "manage_delete_store", PLATFORM_DRIVER) - gevent.sleep(0.1) + gevent.sleep(10) + yield publish_agent -@pytest.fixture(scope="module") -def platform_driver(volttron_instance): - # Start the platform driver agent which would in turn start the bacnet driver - platform_uuid = volttron_instance.install_agent( - agent_dir=get_services_core("PlatformDriverAgent"), - config_file={ - "publish_breadth_first_all": False, - "publish_depth_first": False, - "publish_breadth_first": False, - }, - start=True, - ) - gevent.sleep(2) # wait for the agent to start and start the devices - assert volttron_instance.is_agent_running(platform_uuid) - yield platform_uuid - - volttron_instance.stop_agent(platform_uuid) - if not volttron_instance.debug_mode: - volttron_instance.remove_agent(platform_uuid) + volttron_instance.stop_agent(puid) + publish_agent.core.stop() From f31634390b9581c769e8c266bcebe28b546f6478 Mon Sep 17 00:00:00 2001 From: riley206 Date: Tue, 13 Feb 2024 09:05:17 -0800 Subject: [PATCH 3/3] ssl verify update --- .../home-assistant/HomeAssistantDriver.rst | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/source/agent-framework/driver-framework/home-assistant/HomeAssistantDriver.rst b/docs/source/agent-framework/driver-framework/home-assistant/HomeAssistantDriver.rst index 2e441b6e24..00737324ec 100644 --- a/docs/source/agent-framework/driver-framework/home-assistant/HomeAssistantDriver.rst +++ b/docs/source/agent-framework/driver-framework/home-assistant/HomeAssistantDriver.rst @@ -41,21 +41,34 @@ Examples for lights and thermostats are provided below. Device configuration ++++++++++++++++++++ -Device configuration file contains the connection details to you home assistant instance and driver_type as "home_assistant" +Device configuration file contains the connection details to you home assistant instance. + +- **url**: + Replace ``[Your Home Assistant IP]`` and ``[Your Port]`` with your Home Assistant's IP address and port number, respectively, removing the brackets ``[]``. Ensure you specify the protocol (``http`` or ``https``) based on your setup. Refer to the `Home Assistant documentation `_ for finding your IP address. + +- **access_token**: + Substitute ``[Your Home Assistant Access Token]`` with your actual access token, again removing the brackets ``[]``. For instructions on obtaining your access token, visit `this guide `_. + +- **verify_ssl**: + Set to true to enable SSL certificate verification or false to bypass it. Default is true. Disabling verification may pose security risks. + +- **ssl_cert_path**: + Enter the path to your SSL certificate if you are using a custom certificate for verification. Leave this field empty if you are not using a custom certificate. This field is empty by default. .. code-block:: json - { - "driver_config": { - "ip_address": "Your Home Assistant IP", - "access_token": "Your Home Assistant Access Token", - "port": "Your Port" - }, - "driver_type": "home_assistant", - "registry_config": "config://light.example.json", - "interval": 30, - "timezone": "UTC" - } + { + "driver_config": { + "url": "http://[Your Home Assistant IP]:[Your Port]", + "access_token": "[Your Home Assistant Access Token]", + "verify_ssl": true, + "ssl_cert_path": "" + }, + "driver_type": "home_assistant", + "registry_config": "config://light.example.json", + "interval": 30, + "timezone": "UTC" + } Registry Configuration +++++++++++++++++++++++