diff --git a/.github/workflows/pytest-web.yml b/.github/workflows/pytest-web.yml index 482fbe3160..43b0540aa4 100644 --- a/.github/workflows/pytest-web.yml +++ b/.github/workflows/pytest-web.yml @@ -57,7 +57,7 @@ jobs: # Run the specified tests and save the results to a unique file that can be archived for later analysis. - name: Run pytest on ${{ matrix.python-version }}, ${{ matrix.os }} - uses: volttron/volttron-build-action@v7 + uses: volttron/volttron-build-action@v6 with: python_version: ${{ matrix.python-version }} os: ${{ matrix.os }} diff --git a/services/core/PlatformDriverAgent/platform_driver/interfaces/IEEE2030_5.py b/deprecated/OldPlatformDrivers/Old2030_5Driver/IEEE2030_5.py similarity index 100% rename from services/core/PlatformDriverAgent/platform_driver/interfaces/IEEE2030_5.py rename to deprecated/OldPlatformDrivers/Old2030_5Driver/IEEE2030_5.py diff --git a/deprecated/OldDnp3/OldDnp3Driver/PlatformDriverAgent/platform_driver/interfaces/dnp3.py b/deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driver/PlatformDriverAgent/platform_driver/interfaces/dnp3.py similarity index 100% rename from deprecated/OldDnp3/OldDnp3Driver/PlatformDriverAgent/platform_driver/interfaces/dnp3.py rename to deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driver/PlatformDriverAgent/platform_driver/interfaces/dnp3.py diff --git a/deprecated/OldDnp3/OldDnp3Driver/PlatformDriverAgent/tests/test_dnp3_driver.py b/deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driver/PlatformDriverAgent/tests/test_dnp3_driver.py similarity index 100% rename from deprecated/OldDnp3/OldDnp3Driver/PlatformDriverAgent/tests/test_dnp3_driver.py rename to deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driver/PlatformDriverAgent/tests/test_dnp3_driver.py diff --git a/deprecated/OldDnp3/OldDnp3Driver/dnp3-driver.rst b/deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driver/dnp3-driver.rst similarity index 100% rename from deprecated/OldDnp3/OldDnp3Driver/dnp3-driver.rst rename to deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driver/dnp3-driver.rst diff --git a/deprecated/OldDnp3/OldDnp3Driverexamples/configurations/drivers/dnp3.csv b/deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driverexamples/configurations/drivers/dnp3.csv similarity index 100% rename from deprecated/OldDnp3/OldDnp3Driverexamples/configurations/drivers/dnp3.csv rename to deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driverexamples/configurations/drivers/dnp3.csv diff --git a/deprecated/OldDnp3/OldDnp3Driverexamples/configurations/drivers/test_dnp3.config b/deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driverexamples/configurations/drivers/test_dnp3.config similarity index 100% rename from deprecated/OldDnp3/OldDnp3Driverexamples/configurations/drivers/test_dnp3.config rename to deprecated/OldPlatformDrivers/OldDnp3/OldDnp3Driverexamples/configurations/drivers/test_dnp3.config 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 ac6d51fa10..0e4846dbee 100644 --- a/docs/source/agent-framework/driver-framework/home-assistant/HomeAssistantDriver.rst +++ b/docs/source/agent-framework/driver-framework/home-assistant/HomeAssistantDriver.rst @@ -41,31 +41,44 @@ 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 +++++++++++++++++++++++ Registry file can contain one single device and its attributes or a logical group of devices and its -attributes. Each entry should include the full entity id of the device, including but not limited to home assistant provided prefix +attributes. Each entry should include the full entity id of the device, including but not limited to home assistant provided prefix such as "light.", "climate." etc. The driver uses these prefixes to convert states into integers. Like mentioned before, the driver can only control lights and thermostats but can get data from all devices controlled by home assistant - + Each entry in a registry file should also have a 'Entity Point' and a unique value for 'Volttron Point Name'. The 'Entity ID' maps to the device instance, the 'Entity Point' extracts the attribute or state, and 'Volttron Point Name' determines the name of that point as it appears in VOLTTRON. Attributes can be located in the developer tools in the Home Assistant GUI. @@ -108,7 +121,7 @@ id 'light.example': .. note:: When using a single registry file to represent a logical group of multiple physical entities, make sure the -"Volttron Point Name" is unique within a single registry file. +"Volttron Point Name" is unique within a single registry file. For example, if a registry file contains entities with id 'light.instance1' and 'light.instance2' the entry for the attribute brightness for these two light instances could @@ -174,13 +187,4 @@ Upon completion, initiate the platform driver. Utilize the listener agent to ver 2023-09-12 11:37:00,226 (listeneragent-3.3 211531) __main__ INFO: Peer: pubsub, Sender: platform.driver:, Bus: , Topic: devices/BUILDING/ROOM/light.example/all, Headers: {'Date': '2023-09-12T18:37:00.224648+00:00', 'TimeStamp': '2023-09-12T18:37:00.224648+00:00', 'SynchronizedTimeStamp': '2023-09-12T18:37:00.000000+00:00', 'min_compatible_version': '3.0', 'max_compatible_version': ''}, Message: [{'light_brightness': 254, 'state': 'on'}, {'light_brightness': {'type': 'integer', 'tz': 'UTC', 'units': 'int'}, - 'state': {'type': 'integer', 'tz': 'UTC', 'units': 'On / Off'}}] - -Running Tests -+++++++++++++++++++++++ -To run tests on the VOLTTRON home assistant driver you need to create a helper in your home assistant instance. This can be done by going to **Settings > Devices & services > Helpers > Create Helper > Toggle**. Name this new toggle **volttrontest**. After that run the pytest from the root of your VOLTTRON file. - -.. code-block:: bash - pytest volttron/services/core/PlatformDriverAgent/tests/test_home_assistant.py - -If everything works, you will see 6 passed tests. + 'state': {'type': 'integer', 'tz': 'UTC', 'units': 'On / Off'}}] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 06556a1356..0000000000 --- a/pyproject.toml +++ /dev/null @@ -1,13 +0,0 @@ -[tool.yapfignore] -ignore_patterns = [ - ".env/**", - ".pytest_cache/**", - "dist/**", - "docs/**", -] - -[tool.yapf] -based_on_style = "pep8" -spaces_before_comment = 4 -column_limit = 99 -split_before_logical_operator = true \ No newline at end of file diff --git a/services/core/PlatformDriverAgent/platform_driver/interfaces/home_assistant.py b/services/core/PlatformDriverAgent/platform_driver/interfaces/home_assistant.py index 780b01dab9..dbdd0ab007 100644 --- a/services/core/PlatformDriverAgent/platform_driver/interfaces/home_assistant.py +++ b/services/core/PlatformDriverAgent/platform_driver/interfaces/home_assistant.py @@ -5,7 +5,7 @@ # # ===----------------------------------------------------------------------=== # -# Copyright 2023 Battelle Memorial Institute +# Copyright 2024 Battelle Memorial Institute # # 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 @@ -23,28 +23,32 @@ # }}} -import random -from math import pi import json -import sys -from platform_driver.interfaces import BaseInterface, BaseRegister, BasicRevert -from volttron.platform.agent import utils -from volttron.platform.vip.agent import Agent import logging import requests from requests import get +import urllib3 + +from platform_driver.interfaces import BaseInterface, BaseRegister, BasicRevert +from volttron.platform.agent import utils +from volttron.platform.vip.agent import Agent + _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 @@ -57,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: @@ -72,29 +76,38 @@ 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): - self.ip_address = config_dict.get("ip_address", None) + 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.") + + if not self.verify_ssl: + 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) @@ -112,9 +125,8 @@ 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. if "light." in register.entity_id: @@ -130,7 +142,8 @@ def _set_point(self, point_name, value): 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" @@ -191,10 +204,10 @@ def get_entity_data(self, point_name): "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}" @@ -210,8 +223,8 @@ 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. @@ -237,7 +250,7 @@ def _scrape_all(self): register.value = attribute result[register.point_name] = attribute # handling light states - elif "light." or "input_boolean." in entity_id: # Checks for lights or input bools since they have the same states. + 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. @@ -251,7 +264,7 @@ 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) @@ -288,16 +301,15 @@ def parse_config(self, config_dict): 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) @@ -305,7 +317,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", @@ -316,15 +328,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): @@ -333,10 +343,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 = { @@ -352,14 +362,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, @@ -373,10 +383,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 = { @@ -388,20 +398,18 @@ def change_brightness(self, entity_id, value): def set_input_boolean(self, entity_id, state): service = 'turn_on' if state == 'on' else 'turn_off' - url = f"http://{self.ip_address}:{self.port}/api/services/input_boolean/{service}" + 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 - } + payload = {"entity_id": entity_id} - response = requests.post(url, headers=headers, json=payload) + 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}") + print(f"Failed to set {entity_id} to {state}: {response.text}") \ No newline at end of file diff --git a/services/core/PlatformDriverAgent/tests/test_home_assistant.py b/services/core/PlatformDriverAgent/tests/test_home_assistant.py index 6d7b09b115..459b3e06d4 100644 --- a/services/core/PlatformDriverAgent/tests/test_home_assistant.py +++ b/services/core/PlatformDriverAgent/tests/test_home_assistant.py @@ -5,7 +5,7 @@ # # ===----------------------------------------------------------------------=== # -# Copyright 2023 Battelle Memorial Institute +# Copyright 2024 Battelle Memorial Institute # # 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 @@ -41,55 +41,102 @@ # 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_TEST_IP = "" +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', 'bool_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 making the test fail. -def test_data_poll(volttron_instance: PlatformWrapper, config_store): - expected_values = [{'bool_state': 0}, {'bool_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 = {'bool_state': 1} - agent = volttron_instance.dynamic_agent - agent.vip.rpc.call(PLATFORM_DRIVER, 'set_point', 'home_assistant', 'bool_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": "input_boolean.volttrontest", "Entity Point": "state", @@ -101,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() \ No newline at end of file diff --git a/volttron/platform/__init__.py b/volttron/platform/__init__.py index b56f6f309a..d6ce14cdd2 100644 --- a/volttron/platform/__init__.py +++ b/volttron/platform/__init__.py @@ -35,7 +35,7 @@ from urllib.parse import urlparse from ..utils.frozendict import FrozenDict -__version__ = '8.2' +__version__ = '9.0rc0' _log = logging.getLogger(__name__)