From 0cf49c3d0b870ba90a5273719cb3c617dac26f61 Mon Sep 17 00:00:00 2001 From: "David M. Raker" Date: Tue, 3 Oct 2023 15:09:27 -0700 Subject: [PATCH] Documenation fixes: Updated json formatting, links in readme, and added sphinx dependencies --- README.md | 4 +- docs/source/authentication-endpoints.rst | 8 +- docs/source/index.rst | 4 +- docs/source/platform-endpoints.rst | 10 +- docs/source/platforms/agent-endpoints.rst | 8 +- .../platforms/agents/config-endpoints.rst | 18 +- .../platforms/agents/enabled-endpoints.rst | 8 +- .../platforms/agents/health-endpoints.rst | 4 +- .../source/platforms/agents/rpc-endpoints.rst | 12 +- .../platforms/agents/running-endpoints.rst | 9 +- .../platforms/agents/status-endpoints.rst | 6 +- .../source/platforms/agents/tag-endpoints.rst | 10 +- docs/source/platforms/config-endpoints.rst | 20 +- docs/source/platforms/device-endpoints.rst | 24 +- docs/source/platforms/health-endpoints.rst | 4 +- docs/source/platforms/historian-endpoints.rst | 14 +- docs/source/platforms/pubsub-endpoints.rst | 14 +- docs/source/platforms/status-endpoints.rst | 6 +- pyproject.toml | 29 +- src/volttron/services/web/topic_tree.py | 194 ----------- src/volttron/services/web/vui_endpoints.py | 2 +- tests/unit_tests/test_topic_tree.py | 329 ------------------ tests/unit_tests/test_vui_endpoints.py | 2 +- 23 files changed, 100 insertions(+), 639 deletions(-) delete mode 100644 src/volttron/services/web/topic_tree.py delete mode 100644 tests/unit_tests/test_topic_tree.py diff --git a/README.md b/README.md index 169274f..4e95fe4 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ and web_secret_key should not be included. If SSL is not desired, provide a web_secret_key instead and remove the lines for the web_ssl_cert and web_ssl_key. Any string can be used for the web_secret_key. -Full VOLTTRON documentation is available at [VOLTTRON Readthedocs](https://volttron.readthedocs.io) +Full VOLTTRON documentation is available at [VOLTTRON Readthedocs](https://eclipse-volttron.readthedocs.io/) ## Use @@ -77,7 +77,7 @@ configuration shown above). This will create a file called web-users.json in the Additionally, the web service provides a RESTful API which can be used by other applications. Full documentation for the API is available on -[Readthedocs](https://volttron.readthedocs.io/en/modular/external-docs/volttron-lib-web/docs/source/index.html). +[Readthedocs](https://eclipse-volttron.readthedocs.io/en/latest/external-docs/volttron-lib-web/docs/source/index.html). Note that it is necessary for most API endpoints to have previously created a username and password for authentication by visiting the /admin page or by manually copying or creating a web-users.json file. Authentication can then be diff --git a/docs/source/authentication-endpoints.rst b/docs/source/authentication-endpoints.rst index 15d5e0f..6abb31f 100644 --- a/docs/source/authentication-endpoints.rst +++ b/docs/source/authentication-endpoints.rst @@ -47,7 +47,7 @@ Request: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "username": "", @@ -61,7 +61,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "refresh_token": "", @@ -90,7 +90,7 @@ Request: - Authorization: ``BEARER `` - Body (optional): - .. code-block:: JSON + .. code-block:: javascript { "current_access_token": "" @@ -103,7 +103,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "access_token": "" diff --git a/docs/source/index.rst b/docs/source/index.rst index d792bf6..f530249 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -65,7 +65,7 @@ creating a user and password: .. image:: files/create_admin_user.png -.. code-block:: json +.. code-block:: javascript { "my_user":{ @@ -98,7 +98,7 @@ to be readable and discoverable: Get requests to non-leaf nodes typically return a `route-options` JSON object which gives additional possible paths within the API. For instance, a GET request send to the path `/vui` will return: -.. code-block:: json +.. code-block:: javascript { "route_options": { diff --git a/docs/source/platform-endpoints.rst b/docs/source/platform-endpoints.rst index f9458e2..8514811 100644 --- a/docs/source/platform-endpoints.rst +++ b/docs/source/platform-endpoints.rst @@ -28,6 +28,7 @@ through the following links: All endpoints in this tree require authorization using a JWT bearer token provided by the ``POST /authenticate`` or ``PUT /authenticate`` endpoints. + -------------------------------------------------------------------------------- GET /platforms @@ -51,7 +52,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "route_options": { @@ -64,13 +65,14 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" } * **With invalid BEARER token:** ``401 Unauthorized`` + --------------------------------------------------------------------------------------------------------------------- GET /platforms/:platform @@ -98,7 +100,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "route_options": { @@ -111,7 +113,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/agent-endpoints.rst b/docs/source/platforms/agent-endpoints.rst index 879e13c..96ddf56 100644 --- a/docs/source/platforms/agent-endpoints.rst +++ b/docs/source/platforms/agent-endpoints.rst @@ -50,7 +50,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "route_options": { @@ -63,7 +63,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -91,7 +91,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "route_options": { @@ -104,7 +104,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/agents/config-endpoints.rst b/docs/source/platforms/agents/config-endpoints.rst index ebe03d8..ded16a8 100644 --- a/docs/source/platforms/agents/config-endpoints.rst +++ b/docs/source/platforms/agents/config-endpoints.rst @@ -30,7 +30,7 @@ Response - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "route_options": { @@ -43,7 +43,7 @@ Response - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -54,7 +54,7 @@ Response -------------------------------------------------------------------------------------------------- POST /platforms/:platform/agents/:vip_identity/configs/ -======================================================= +======================================================== Save a new configuration file to the config store. @@ -86,7 +86,7 @@ Response - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -97,7 +97,7 @@ Response ----------------------------------------------------------------------------------------- DELETE /platforms/:platform/agents/:vip_identity/configs/ -===================================================================== +========================================================== Remove the configuration store for an agent. This endpoint will return ``409 Conflict`` if the store for this agent is not empty. To remove all existing configurations for an agent from the config store @@ -119,7 +119,7 @@ Response - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -155,7 +155,7 @@ Response - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -190,7 +190,7 @@ Response - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -219,7 +219,7 @@ Response - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/agents/enabled-endpoints.rst b/docs/source/platforms/agents/enabled-endpoints.rst index f104f0e..5f94d03 100644 --- a/docs/source/platforms/agents/enabled-endpoints.rst +++ b/docs/source/platforms/agents/enabled-endpoints.rst @@ -31,7 +31,7 @@ Response: - Content Type: application/json - Body: - .. code-block:: json + .. code-block:: javascript { "status": true|false, @@ -41,7 +41,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -75,7 +75,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -105,7 +105,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/agents/health-endpoints.rst b/docs/source/platforms/agents/health-endpoints.rst index b99195e..e923202 100644 --- a/docs/source/platforms/agents/health-endpoints.rst +++ b/docs/source/platforms/agents/health-endpoints.rst @@ -30,7 +30,7 @@ Response: - Content Type: application/json - Body: - .. code-block:: json + .. code-block:: javascript { "status": "", @@ -41,7 +41,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/agents/rpc-endpoints.rst b/docs/source/platforms/agents/rpc-endpoints.rst index eb69c6b..d04772c 100644 --- a/docs/source/platforms/agents/rpc-endpoints.rst +++ b/docs/source/platforms/agents/rpc-endpoints.rst @@ -33,7 +33,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "route_options": { @@ -46,7 +46,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -81,7 +81,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "params": { @@ -106,7 +106,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -132,7 +132,7 @@ Request: * Authorization: ``BEARER `` * Body: - .. code-block:: JSON + .. code-block:: javascript { "": "", @@ -150,7 +150,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/agents/running-endpoints.rst b/docs/source/platforms/agents/running-endpoints.rst index fcb76d2..97e302a 100644 --- a/docs/source/platforms/agents/running-endpoints.rst +++ b/docs/source/platforms/agents/running-endpoints.rst @@ -30,16 +30,17 @@ Response: - Content Type: application/json - Body: - .. code-block:: json + .. code-block:: javascript { "status": true|false } + * **With valid BEARER token on failure:** ``400 Bad Request`` - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -73,7 +74,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -103,7 +104,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/agents/status-endpoints.rst b/docs/source/platforms/agents/status-endpoints.rst index a2294ed..d7de199 100644 --- a/docs/source/platforms/agents/status-endpoints.rst +++ b/docs/source/platforms/agents/status-endpoints.rst @@ -14,7 +14,7 @@ a single agent running on a VOLTTRON platform. Only a GET method is currently im -------------- GET /platforms/:platform/agents/:agent/status -=============================== +============================================= Get status for a specific agent on the platform. @@ -30,7 +30,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: json + .. code-block:: javascript { "name": "", @@ -47,7 +47,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/agents/tag-endpoints.rst b/docs/source/platforms/agents/tag-endpoints.rst index 95747de..010a5a5 100644 --- a/docs/source/platforms/agents/tag-endpoints.rst +++ b/docs/source/platforms/agents/tag-endpoints.rst @@ -30,7 +30,7 @@ Response: - Content Type: application/json - Body: - .. code-block:: json + .. code-block:: javascript { "tag": "" @@ -39,7 +39,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -62,7 +62,7 @@ Request: * Content Type: ``application/json`` * Body: - .. code-block:: json + .. code-block:: javascript { "tag": "" @@ -77,7 +77,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -107,7 +107,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/config-endpoints.rst b/docs/source/platforms/config-endpoints.rst index c42c2ad..f5a5275 100644 --- a/docs/source/platforms/config-endpoints.rst +++ b/docs/source/platforms/config-endpoints.rst @@ -1,8 +1,8 @@ .. _Platforms-Configs-Endpoints: -========================== +=========================== Platforms Configs Endpoints -========================== +=========================== Platforms Configs endpoints expose functionality associated with platform configuration files. These endpoints are for platform-level configurations. Agent configurations are managed by @@ -31,7 +31,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "route_options": { @@ -44,7 +44,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -55,7 +55,7 @@ Response: --------------------------------------------------------------- POST /platforms/:platform/configs -================================ +================================= Save a new platform configuration file. @@ -99,7 +99,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -130,7 +130,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "": , @@ -151,7 +151,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -203,7 +203,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -240,7 +240,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/device-endpoints.rst b/docs/source/platforms/device-endpoints.rst index 3cc6069..33eb99d 100644 --- a/docs/source/platforms/device-endpoints.rst +++ b/docs/source/platforms/device-endpoints.rst @@ -83,7 +83,7 @@ Response: where two segments were provided (the topic provided was `MyCampus/Building1`. Devices within the building are returned: - .. code-block:: JSON + .. code-block:: javascript { "route_options": { @@ -98,7 +98,7 @@ Response: ``read-all`` does not need to be ``true`` for this case to get data, as a point segment was provided. Other query parameters were not provided or were set to their default values. - .. code-block:: JSON + .. code-block:: javascript { "MyCampus/Building1/Device1/Point4": { @@ -118,7 +118,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -153,7 +153,7 @@ Request: * Body: - .. code-block:: JSON + .. code-block:: javascript { "value": @@ -168,7 +168,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "": { @@ -184,7 +184,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "": { @@ -203,7 +203,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -215,7 +215,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -260,7 +260,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "": { @@ -275,7 +275,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "": { @@ -293,7 +293,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -305,7 +305,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/health-endpoints.rst b/docs/source/platforms/health-endpoints.rst index 6677f81..74e2cd5 100644 --- a/docs/source/platforms/health-endpoints.rst +++ b/docs/source/platforms/health-endpoints.rst @@ -30,7 +30,7 @@ Response: - Content Type: application/json - Body: - .. code-block:: json + .. code-block:: javascript { "": { @@ -46,7 +46,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/historian-endpoints.rst b/docs/source/platforms/historian-endpoints.rst index 70b5abd..ef9fd74 100644 --- a/docs/source/platforms/historian-endpoints.rst +++ b/docs/source/platforms/historian-endpoints.rst @@ -59,7 +59,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "": "/vui/platforms/:platform/historians/:historian", @@ -72,7 +72,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -101,7 +101,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "topics": "/vui/platforms/:platform/historians/:historian/topics" @@ -113,7 +113,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -180,7 +180,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "Campus/Building1/Fake2/SampleWritableFloat1": { @@ -204,7 +204,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "Campus/Building1/Fake2/SampleWritableFloat1": { @@ -231,7 +231,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/pubsub-endpoints.rst b/docs/source/platforms/pubsub-endpoints.rst index 411c081..304cf80 100644 --- a/docs/source/platforms/pubsub-endpoints.rst +++ b/docs/source/platforms/pubsub-endpoints.rst @@ -28,7 +28,7 @@ Response: - **With valid BEARER token on success:** ``200 OK`` - Body: - .. code-block:: JSON + .. code-block:: javascript [ "/vui/platform/:platform/pubsub/:topic", @@ -41,7 +41,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -89,7 +89,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -119,7 +119,7 @@ Request: - Body: - .. code-block:: JSON + .. code-block:: javascript { "headers": {}, @@ -135,7 +135,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "number_of_subscribers": @@ -147,7 +147,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -181,7 +181,7 @@ Response: - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/docs/source/platforms/status-endpoints.rst b/docs/source/platforms/status-endpoints.rst index 5ae1494..b442034 100644 --- a/docs/source/platforms/status-endpoints.rst +++ b/docs/source/platforms/status-endpoints.rst @@ -30,7 +30,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: json + .. code-block:: javascript { "": { @@ -50,7 +50,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" @@ -80,7 +80,7 @@ Response: - Content Type: ``application/json`` - Body: - .. code-block:: JSON + .. code-block:: javascript { "error": "" diff --git a/pyproject.toml b/pyproject.toml index 14c4885..15cd3cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ passlib = "^1.7.4" PyJWT = "==1.7.1" python = ">=3.10,<4.0" treelib = ">=1.6.1" -volttron = ">=10.0.2rc0,<11.0" +volttron = ">=10.0.5rc0,<11.0" werkzeug = ">=2.1.2" ws4py = ">=0.5.1" @@ -49,31 +49,12 @@ git-changelog = ">=0.5.0" httpx = ">=0.16.1" toml = ">=0.10.2" -# flake8 plugins -#darglint = ">=1.5.8" -#flake8 = ">=3.7.0" -#flake8-bandit = ">=2.1.2" -#flake8-black = ">=0.2.1" -#flake8-bugbear = ">=20.11.1" -#flake8-builtins = ">=1.5.3" -#flake8-comprehensions = ">=3.3.1" -#flake8-docstrings = ">=1.5.0" -#flake8-pytest-style = ">=1.3.0" -#flake8-string-format = ">=0.3.0" -#flake8-tidy-imports = ">=4.2.1" -#flake8-variables-names = ">=0.0.4" -#pep8-naming = ">=0.11.1" -#pydocstyle = ">=6.1.1" -#wps-light = ">=0.15.2" - -# docs -#mkdocs = ">=1.2.2" -#mkdocs-coverage = ">=0.2.1" -#mkdocs-macros-plugin = ">=0.5.0" -#mkdocs-material = ">=6.2.7" -#mkdocstrings = ">=0.16.2" pre-commit = "^2.17.0" +[tool.poetry.group.documentation.dependencies] +Sphinx = "^4.5.0" +sphinx-rtd-theme = "^1.0.0" + [tool.isort] line_length = 120 not_skip = "__init__.py" diff --git a/src/volttron/services/web/topic_tree.py b/src/volttron/services/web/topic_tree.py deleted file mode 100644 index b1ff6e7..0000000 --- a/src/volttron/services/web/topic_tree.py +++ /dev/null @@ -1,194 +0,0 @@ -# -*- coding: utf-8 -*- {{{ -# ===----------------------------------------------------------------------=== -# -# Installable Component of Eclipse VOLTTRON -# -# ===----------------------------------------------------------------------=== -# -# Copyright 2022 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 -# of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# 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. -# -# ===----------------------------------------------------------------------=== -# }}} - -from typing import Union, Iterable -from treelib import Tree, Node -from treelib.exceptions import DuplicatedNodeIdError, NodeIDAbsentError -from collections import defaultdict - -from volttron.client.known_identities import CONFIGURATION_STORE - -import re -from os.path import normpath - -import logging -_log = logging.getLogger(__name__) - - -class TopicNode(Node): - def __init__(self, tag=None, identifier=None, expanded=True, data=None, segment_type='TOPIC_SEGMENT', topic=''): - super(TopicNode, self).__init__(tag, identifier, expanded, data) - self.segment_type = segment_type - self.topic = topic - - def is_segment(self): - return True if self.segment_type == 'TOPIC_SEGMENT' else False - - -class TopicTree(Tree): - def __init__(self, topic_list=None, root_name='root', node_class=None, *args, **kwargs): - node_class = node_class if node_class else TopicNode - super(TopicTree, self).__init__(node_class=node_class, *args, **kwargs) - if topic_list: - self._from_topic_list(topic_list, root_name) - else: - self.create_node(root_name, root_name).segment_type = 'TOPIC_ROOT' - - def _from_topic_list(self, topic_list, root_name): - tops = [t.split('/') for t in topic_list] - if all([top[0] == root_name for top in tops]): - [top.pop(0) for top in tops] - self.create_node(root_name, root_name).segment_type = 'TOPIC_ROOT' - for top in tops: - parent = root_name - for segment in top: - nid = '/'.join([parent, segment]) - try: - self.create_node(segment, nid, parent=parent) - except DuplicatedNodeIdError: - pass - parent = nid - - def add_node(self, node, parent=None): - super(TopicTree, self).add_node(node, parent) - node.topic = node.identifier[(len(self.root) + 1):] - return node - - # TODO: Should this actually be get_child_topics() where topics or routes are returned with wildcards? - def get_children_dict(self, sub_root_node_id: Union[list, str], include_root: bool = True, - prefix: str = '', replace_topic: str = None) -> dict: - sub_root_node_id = sub_root_node_id if type(sub_root_node_id) is list else [sub_root_node_id] - level_dict = defaultdict(set) - for r_id in sub_root_node_id: - for d in self.children(r_id): - try: - if replace_topic: - if include_root: - level_dict[d.tag].add('/'.join([self.root, replace_topic, d.tag])) - else: - level_dict[d.tag].add('/'.join([replace_topic, d.tag])) - else: - if include_root: - level_dict[d.tag].add(d.identifier) - else: - level_dict[d.tag].add(d.identifier.split('/', 1)[1]) - except NodeIDAbsentError as e: - return {} - ret_dict = {} - for k, s in level_dict.items(): - if len(s) > 1: - ret_dict[k] = sorted([normpath('/'.join([prefix, v])) for v in s]) - else: - ret_dict[k] = normpath('/'.join([prefix, s.pop()])) - return ret_dict - - def prune(self, topic_pattern: str = None, regex: str = None, exact_matches: Iterable = None, *args, **kwargs): - if topic_pattern: - pattern = re.compile(topic_pattern.replace('-', '[^/]+') + '(/|$)') - nids = [n.identifier for n in self.filter_nodes(lambda x: pattern.search(x.identifier))] - else: - nids = list(self.expand_tree()) - if regex: - regex = re.compile(regex) - nids = [n for n in nids if regex.search(n)] - if exact_matches: - nids = [n for n in nids if n in exact_matches] - pruned = self.__class__(topic_list=nids, root_name=self.root, *args, **kwargs) - for nid in [n.identifier for n in pruned.all_nodes()]: - old = self.get_node(nid) - pruned.update_node(nid, data=old.data, segment_type=old.segment_type) - return pruned - - def get_matches(self, topic, return_nodes=True): - pattern = topic.replace('-', '[^/]+') + '$' - nodes = self.filter_nodes(lambda x: re.match(pattern, x.identifier)) - if return_nodes: - return list(nodes) - else: - return [n.identifier for n in nodes] - - -class DeviceNode(TopicNode): - def __init__(self, tag=None, identifier=None, expanded=True, data=None, segment_type='TOPIC_SEGMENT', topic=''): - super(DeviceNode, self).__init__(tag, identifier, expanded, data, segment_type, topic) - - def is_point(self): - return True if self.segment_type == 'POINT' else False - - def is_device(self): - return True if self.segment_type == 'DEVICE' else False - - -class DeviceTree(TopicTree): - def __init__(self, topic_list=None, root_name='devices', assume_full_topics=False, *args, **kwargs): - super(DeviceTree, self).__init__(topic_list=topic_list, root_name=root_name, node_class=DeviceNode, - *args, **kwargs) - if assume_full_topics: - for n in self.leaves(): - n.segment_type = 'POINT' - for n in [self.parent(l.identifier) for l in self.leaves()]: - n.segment_type = 'DEVICE' - - def points(self, nid=None): - if nid is None: - points = [n for n in self._nodes.values() if n.is_point()] - else: - points = [self[n] for n in self.expand_tree(nid) if self[n].is_point()] - return points - - def devices(self, nid=None): - if nid is None: - points = [n for n in self._nodes.values() if n.is_device()] - else: - points = [self[n] for n in self.expand_tree(nid) if self[n].is_device()] - return points - - # TODO: Getting points requires getting device config, using it to find the registry config, - # and then parsing that. There is not a method in config.store, nor in the platform.driver for - # getting a completed configuration. The configuration is only fully assembled in the subsystem's - # _initial_update method called when the agent itself calls get_configs at startup. There does not - # seem to be an equivalent management method, and the code for this is in the agent subsystem - # rather than the service (though it is reached through the service, oddly... - @classmethod - def from_store(cls, platform, rpc_caller): - # TODO: Duplicate logic for external_platform check from VUIEndpoints to remove reference to it from here. - kwargs = {'external_platform': platform} if 'VUIEndpoints' in rpc_caller.__repr__() else {} - devices = rpc_caller(CONFIGURATION_STORE, 'manage_list_configs', 'platform.driver', **kwargs) - devices = devices if kwargs else devices.get(timeout=5) - devices = [d for d in devices if re.match('^devices/.*', d)] - device_tree = cls(devices) - for d in devices: - dev_config = rpc_caller(CONFIGURATION_STORE, 'manage_get', 'platform.driver', d, raw=False, **kwargs) - # TODO: If not AsyncResponse instead of if kwargs - dev_config = dev_config if kwargs else dev_config.get(timeout=5) - reg_cfg_name = dev_config.pop('registry_config')[len('config://'):] - device_tree.update_node(d, data=dev_config, segment_type='DEVICE') - registry_config = rpc_caller('config.store', 'manage_get', 'platform.driver', - f'{reg_cfg_name}', raw=False, **kwargs) - registry_config = registry_config if kwargs else registry_config.get(timeout=5) - for pnt in registry_config: - point_name = pnt.pop('Volttron Point Name') - n = device_tree.create_node(point_name, f"{d}/{point_name}", parent=d, data=pnt) - n.segment_type = 'POINT' - return device_tree diff --git a/src/volttron/services/web/vui_endpoints.py b/src/volttron/services/web/vui_endpoints.py index 9aa4ffb..a8747fd 100644 --- a/src/volttron/services/web/vui_endpoints.py +++ b/src/volttron/services/web/vui_endpoints.py @@ -37,7 +37,7 @@ from volttron.client.known_identities import CONFIGURATION_STORE, CONTROL from volttron.client.vip.agent.subsystems.query import Query from volttron.utils.jsonrpc import MethodNotFound, RemoteError -from .topic_tree import DeviceTree, TopicTree +from volttron.lib.topic_tree import DeviceTree, TopicTree from .vui_pubsub import VUIPubsubManager diff --git a/tests/unit_tests/test_topic_tree.py b/tests/unit_tests/test_topic_tree.py deleted file mode 100644 index b7a24e1..0000000 --- a/tests/unit_tests/test_topic_tree.py +++ /dev/null @@ -1,329 +0,0 @@ -# -*- coding: utf-8 -*- {{{ -# ===----------------------------------------------------------------------=== -# -# Installable Component of Eclipse VOLTTRON -# -# ===----------------------------------------------------------------------=== -# -# Copyright 2022 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 -# of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# 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. -# -# ===----------------------------------------------------------------------=== -# }}} - -import pytest -from uuid import UUID -from volttron.services.web.topic_tree import TopicNode, TopicTree, DeviceNode, DeviceTree - - -TOPIC_LIST = ['Campus/Building1/Fake1/SampleWritableFloat1', 'Campus/Building1/Fake1/SampleBool1', - 'Campus/Building2/Fake1/SampleWritableFloat1', 'Campus/Building2/Fake1/SampleBool1', - 'Campus/Building3/Fake1/SampleWritableFloat1', 'Campus/Building3/Fake1/SampleBool1'] - -ALL_IDENTIFIERS = ['root', 'root/Campus', 'root/Campus/Building1', 'root/Campus/Building1/Fake1', - 'root/Campus/Building1/Fake1/SampleWritableFloat1', 'root/Campus/Building1/Fake1/SampleBool1', - 'root/Campus/Building2', 'root/Campus/Building2/Fake1', - 'root/Campus/Building2/Fake1/SampleWritableFloat1', 'root/Campus/Building2/Fake1/SampleBool1', - 'root/Campus/Building3', 'root/Campus/Building3/Fake1', - 'root/Campus/Building3/Fake1/SampleWritableFloat1', 'root/Campus/Building3/Fake1/SampleBool1'] - - -def _is_valid_uuid(uuid_to_test): - try: - uuid_obj = UUID(uuid_to_test) - except ValueError: - return False - return str(uuid_obj) == uuid_to_test - - -def test_topic_node_init(): - n = TopicNode() - assert _is_valid_uuid(n.tag) - assert _is_valid_uuid(n.identifier) - assert n.expanded is True - assert n.data is None - assert n.segment_type == 'TOPIC_SEGMENT' - assert n.topic == '' - - n = TopicNode(tag='foo', identifier='bar', expanded=False, data={'foo': 'bar'}, segment_type='OTHER', - topic='foo/bar') - assert n.tag == 'foo' - assert n.identifier == 'bar' - assert n.expanded is False - assert n.data == {'foo': 'bar'} - assert n.segment_type == 'OTHER' - assert n.topic == 'foo/bar' - - -@pytest.mark.parametrize('segment_type, expected', [('TOPIC_SEGMENT', True), ('TOPIC_ROOT', False), ('POINT', False), - ('DEVICE', False)]) -def test_is_segment(segment_type, expected): - n = TopicNode(segment_type=segment_type) - assert n.is_segment() is expected - - -def test_topic_tree_init(): - t = TopicTree() - assert len(t) == 1 - assert all([n.is_root() for n in t.all_nodes()]) - assert all([n.tag == 'root' and n.identifier == 'root' for n in t.all_nodes()]) - assert t.node_class == TopicNode - assert all([isinstance(n, TopicNode) for n in t.all_nodes()]) - t = TopicTree(topic_list=TOPIC_LIST) - assert len(t) == 14 - assert len(t.leaves()) == 6 - assert all([isinstance(n, TopicNode) for n in t.all_nodes()]) - - -def test__from_topic_list(): - t = TopicTree() - t.remove_node(t.root) - t._from_topic_list(TOPIC_LIST, 'Campus') - assert len(t) == 13 - assert t.get_node('Campus').segment_type == 'TOPIC_ROOT' - buildings = ['Campus/Building1', 'Campus/Building2', 'Campus/Building3'] - assert all([t.get_node(n).segment_type == 'TOPIC_SEGMENT' for n in buildings]) - assert all([t.level(n) == 1 for n in buildings]) - devices = ['Campus/Building1/Fake1', 'Campus/Building2/Fake1', 'Campus/Building3/Fake1'] - assert all([t.get_node(n).segment_type == 'TOPIC_SEGMENT' for n in buildings]) - assert all([t.level(n) == 2 for n in devices]) - assert all([t.get_node(n).segment_type == 'TOPIC_SEGMENT' for n in TOPIC_LIST]) - assert all([t.level(n) == 3 for n in TOPIC_LIST]) - assert all([top[7:] in [n.topic for n in t.leaves()] for top in TOPIC_LIST]) - assert TOPIC_LIST == [n.identifier for n in t.leaves()] - # Test duplicates ignored: - t.remove_node(t.root) - t._from_topic_list(TOPIC_LIST + ['Campus/Building1/Fake1/SampleBool1'], 'Campus') - assert len(t) == 13 - - -def test_add_node(): - t = TopicTree() - n = TopicNode(tag='foo', identifier='bar') - t.add_node(n, t.root) - assert len(t) == 2 - assert t.level('bar') == 1 - assert t.parent('bar') is t.get_node(t.root) - - -def test_get_children_dict(): - t = TopicTree(topic_list=TOPIC_LIST) - # Test response to single sub_root__node_id - kids = t.get_children_dict('root/Campus/Building1/Fake1') - assert kids == {'SampleWritableFloat1': '/root/Campus/Building1/Fake1/SampleWritableFloat1', - 'SampleBool1': '/root/Campus/Building1/Fake1/SampleBool1'} - # Test response to multiple sub_root__node_ids - kids = t.get_children_dict(['root/Campus/Building1/Fake1', 'root/Campus/Building1']) - assert kids == {'SampleWritableFloat1': '/root/Campus/Building1/Fake1/SampleWritableFloat1', - 'SampleBool1': '/root/Campus/Building1/Fake1/SampleBool1', - 'Fake1': '/root/Campus/Building1/Fake1'} - # Test include_root == True - assert all([v[:6] == '/root/' for v in kids.values()]) - # Test include_root == False - kids = t.get_children_dict(['root/Campus/Building1/Fake1', 'root/Campus/Building1'], include_root=False) - assert all('root' not in v for v in kids.values()) - # Test with prefix: - kids = t.get_children_dict(['root/Campus/Building1/Fake1', 'root/Campus/Building1'], prefix='/foo/bar') - assert all([v[:14] == '/foo/bar/root/' for v in kids.values()]) - # Test replace topic: - kids = t.get_children_dict(['root/Campus/Building1/Fake1', 'root/Campus/Building1'], replace_topic='foo/bar') - assert kids == {'SampleWritableFloat1': '/root/foo/bar/SampleWritableFloat1', - 'SampleBool1': '/root/foo/bar/SampleBool1', - 'Fake1': '/root/foo/bar/Fake1'} - - -def test_get_matches(): - t = TopicTree(TOPIC_LIST) - nodes = [t.get_node('root/Campus/Building1/Fake1/SampleWritableFloat1'), - t.get_node('root/Campus/Building1/Fake1/SampleBool1')] - assert nodes == t.get_matches('root/Campus/Building1/Fake1/-') - # Test return_nodes == False - idents = ['root/Campus/Building1/Fake1/SampleWritableFloat1', 'root/Campus/Building1/Fake1/SampleBool1'] - assert idents == t.get_matches('root/Campus/Building1/Fake1/-', return_nodes=False) - - -@pytest.mark.parametrize('topic_pattern, regex, exact_matches, included', - [ - ('', '', [], ALL_IDENTIFIERS), - ('root/Campus/Building1/Fake1/-', '', [], - ['root', 'root/Campus', 'root/Campus/Building1', 'root/Campus/Building1/Fake1', - 'root/Campus/Building1/Fake1/SampleWritableFloat1', 'root/Campus/Building1/Fake1/SampleBool1']), - ('', '.*Bool.*', [], - ['root', 'root/Campus', 'root/Campus/Building1', 'root/Campus/Building1/Fake1', - 'root/Campus/Building1/Fake1/SampleBool1', 'root/Campus/Building2', 'root/Campus/Building2/Fake1', - 'root/Campus/Building2/Fake1/SampleBool1', 'root/Campus/Building3', 'root/Campus/Building3/Fake1', - 'root/Campus/Building3/Fake1/SampleBool1']), - ('root/Campus/Building1/Fake1/-', '.*Bool.*', [], - ['root', 'root/Campus', 'root/Campus/Building1', 'root/Campus/Building1/Fake1', - 'root/Campus/Building1/Fake1/SampleBool1']), - ('', '', ['root/Campus/Building1/Fake1/SampleBool1', 'root/Campus/Building1/Fake1/SampleWritableFloat1', - 'root/Campus/Building3'], - ['root', 'root/Campus', 'root/Campus/Building1', 'root/Campus/Building1/Fake1', - 'root/Campus/Building1/Fake1/SampleBool1', 'root/Campus/Building1/Fake1/SampleWritableFloat1', - 'root/Campus/Building3']), - ('', '.*(Building1|Building3).*', [], - ['root', 'root/Campus', 'root/Campus/Building1', 'root/Campus/Building1/Fake1', - 'root/Campus/Building1/Fake1/SampleBool1', 'root/Campus/Building1/Fake1/SampleWritableFloat1', - 'root/Campus/Building3', 'root/Campus/Building3/Fake1', 'root/Campus/Building3/Fake1/SampleBool1', - 'root/Campus/Building3/Fake1/SampleWritableFloat1']), - ('', '.*(Building1|Building3).*', ['root/Campus/Building1/Fake1/SampleWritableFloat1'], - ['root', 'root/Campus', 'root/Campus/Building1', 'root/Campus/Building1/Fake1', - 'root/Campus/Building1/Fake1/SampleWritableFloat1']) - ] -) -def test_prune(topic_pattern, regex, exact_matches, included): - excluded = [i for i in ALL_IDENTIFIERS if i not in included] - t = TopicTree(TOPIC_LIST) - pruned = t.prune(topic_pattern=topic_pattern, regex=regex, exact_matches=exact_matches) - assert isinstance(pruned, TopicTree) - assert all([n.identifier in included for n in pruned.all_nodes()]) - assert all([n.identifier not in excluded for n in pruned.all_nodes()]) - assert [n.segment_type == t.get_node(n.identifier) for n in pruned.all_nodes()] - assert [n.data == t.get_node(n.data) for n in pruned.all_nodes()] - - -def test_prune_final_segments(): - t = TopicTree(TOPIC_LIST + ['Campus/Building1/Fake1/EKG', 'Campus/Building1/Fake1/EKG_Sin', - 'Campus/Building1/Fake1/EKG_Cos']) - pruned = t.prune(topic_pattern='root/Campus/Building1/Fake1/EKG') - included = ['root', 'root/Campus', 'root/Campus/Building1', 'root/Campus/Building1/Fake1', - 'root/Campus/Building1/Fake1/EKG'] - assert [n.identifier for n in pruned.all_nodes()] == included - - -def test_device_node_init(): - n = DeviceNode() - assert _is_valid_uuid(n.tag) - assert _is_valid_uuid(n.identifier) - assert n.expanded is True - assert n.data is None - assert n.segment_type == 'TOPIC_SEGMENT' - assert n.topic == '' - - n = DeviceNode(tag='foo', identifier='bar', expanded=False, data={'foo': 'bar'}, segment_type='OTHER', - topic='foo/bar') - assert n.tag == 'foo' - assert n.identifier == 'bar' - assert n.expanded is False - assert n.data == {'foo': 'bar'} - assert n.segment_type == 'OTHER' - assert n.topic == 'foo/bar' - - -@pytest.mark.parametrize('segment_type, expected', [('TOPIC_SEGMENT', False), ('TOPIC_ROOT', False), ('POINT', False), - ('DEVICE', True)]) -def test_is_device(segment_type, expected): - n = DeviceNode(segment_type=segment_type) - assert n.is_device() is expected - - -@pytest.mark.parametrize('segment_type, expected', [('TOPIC_SEGMENT', False), ('TOPIC_ROOT', False), ('POINT', True), - ('DEVICE', False)]) -def test_is_point(segment_type, expected): - n = DeviceNode(segment_type=segment_type) - assert n.is_point() is expected - - -def test_device_tree_init(): - t = DeviceTree() - assert len(t) == 1 - assert all([n.is_root() for n in t.all_nodes()]) - assert all([n.tag == 'devices' and n.identifier == 'devices' for n in t.all_nodes()]) - assert t.node_class == DeviceNode - assert all([isinstance(n, DeviceNode) for n in t.all_nodes()]) - t = DeviceTree(topic_list=TOPIC_LIST) - assert len(t) == 14 - assert len(t.leaves()) == 6 - assert all([isinstance(n, DeviceNode) for n in t.all_nodes()]) - assert t.get_node(t.root).identifier == 'devices' - t = DeviceTree(topic_list=TOPIC_LIST, assume_full_topics=True) - assert all([n.segment_type == 'POINT' for n in t.leaves()]) - assert all([t.parent(n.identifier).segment_type == 'DEVICE' for n in t.leaves()]) - - -@pytest.mark.parametrize( - 'nid, expected', - [ - (None, ['devices/Campus/Building1/Fake1', 'devices/Campus/Building2/Fake1', - 'devices/Campus/Building3/Fake1']), - ('devices/Campus/Building2', ['devices/Campus/Building2/Fake1']) - ]) -def test_devices(nid, expected): - t = DeviceTree(topic_list=TOPIC_LIST, assume_full_topics=True) - assert [n.identifier for n in t.devices(nid)] == expected - - -def _mock_rpc_caller(peer, method, agent, file_name=None, raw=False, external_platform=None): - if method == 'manage_list_configs': - return ['config', 'devices/Campus/Building1/Fake1', 'devices/Campus/Building2/Fake1', - 'devices/Campus/Building3/Fake1', 'registry_configs/fake.csv'] - elif method == 'manage_get' and '.csv' in file_name: - return [{'Point Name': 'SampleBool1', 'Volttron Point Name': 'SampleBool1', 'Units': 'On / Off', - 'Units Details': 'on/off', 'Writable': 'FALSE', 'Starting Value': 'TRUE', 'Type': 'boolean', - 'Notes': 'Status indidcator of cooling stage 1'}, - {'Point Name': 'SampleWritableFloat1', 'Volttron Point Name': 'SampleWritableFloat1', 'Units': 'PPM', - 'Units Details': '1000.00 (default)', 'Writable': 'TRUE', 'Starting Value': '10', 'Type': 'float', - 'Notes': 'Setpoint to enable demand control ventilation'}] - elif method == 'manage_get' and '.csv' not in file_name: - return {'driver_config': {}, 'registry_config': 'config://registry_configs/fake.csv', 'interval': 60, - 'timezone': 'US/Pacific', 'driver_type': 'fakedriver', 'publish_breadth_first_all': False, - 'publish_depth_first': False, 'publish_breadth_first': False, 'campus': 'campus', - 'building': 'building', 'unit': 'fake_device'} - else: - return None - - -_mock_rpc_caller.__repr__ = lambda: 'VUIEndpoints' - - -def test_from_store(): - t = DeviceTree.from_store('my_instance_name', _mock_rpc_caller) - assert len(t) == 14 - assert len(t.leaves()) == 6 - assert all([isinstance(n, DeviceNode) for n in t.all_nodes()]) - assert t.get_node(t.root).identifier == 'devices' - t = DeviceTree(topic_list=TOPIC_LIST, assume_full_topics=True) - assert all([n.segment_type == 'POINT' for n in t.leaves()]) - assert all([t.parent(n.identifier).segment_type == 'DEVICE' for n in t.leaves()]) - assert all([n.data == {'Point Name': 'SampleBool1', 'Units': 'On / Off', 'Units Details': 'on/off', - 'Writable': 'FALSE', 'Starting Value': 'TRUE', 'Type': 'boolean', - 'Notes': 'Status indidcator of cooling stage 1'} - for n in t.get_matches('Campus/-/Fake1/SampleBool1')]) - assert all([n.data == {'Point Name': 'SampleWritableFloat1', 'Units': 'PPM', 'Units Details': '1000.00 (default)', - 'Writable': 'TRUE', 'Starting Value': '10', 'Type': 'float', - 'Notes': 'Setpoint to enable demand control ventilation'} - for n in t.get_matches('Campus/-/Fake1/SampleWritableFloat1')]) - assert all(n.data == {'driver_config': {}, 'registry_config': 'config://registry_configs/fake.csv', 'interval': 60, - 'timezone': 'US/Pacific', 'driver_type': 'fakedriver', 'publish_breadth_first_all': False, - 'publish_depth_first': False, 'publish_breadth_first': False, 'campus': 'campus', - 'building': 'building', 'unit': 'fake_device'} - for n in t.get_matches('Campus/-/Fake1')) - - -@pytest.mark.parametrize( - 'nid, expected', - [ - (None, ['devices/Campus/Building1/Fake1/SampleWritableFloat1', - 'devices/Campus/Building1/Fake1/SampleBool1', - 'devices/Campus/Building2/Fake1/SampleWritableFloat1', - 'devices/Campus/Building2/Fake1/SampleBool1', - 'devices/Campus/Building3/Fake1/SampleWritableFloat1', - 'devices/Campus/Building3/Fake1/SampleBool1']), - ('devices/Campus/Building2', - ['devices/Campus/Building2/Fake1/SampleBool1', - 'devices/Campus/Building2/Fake1/SampleWritableFloat1']) - ]) -def test_points(nid, expected): - t = DeviceTree(topic_list=TOPIC_LIST, assume_full_topics=True) - assert [n.identifier for n in t.points(nid)] == expected diff --git a/tests/unit_tests/test_vui_endpoints.py b/tests/unit_tests/test_vui_endpoints.py index 65da6bc..6edf14e 100644 --- a/tests/unit_tests/test_vui_endpoints.py +++ b/tests/unit_tests/test_vui_endpoints.py @@ -66,7 +66,7 @@ } # TODO: Need new DEV_TREE: This pickle was built on monolithic. -DEV_TREE = b'\x80\x04\x952\x11\x00\x00\x00\x00\x00\x00\x8c volttron.services.web.topic_tree\x94\x8c\nDeviceTree\x94\x93\x94)\x81\x94}\x94(\x8c\x0b_identifier\x94\x8c$43dd9fcc-0066-11ec-afd5-f834419e14e3\x94\x8c\nnode_class\x94h\x00\x8c\nDeviceNode\x94\x93\x94\x8c\x06_nodes\x94}\x94(\x8c\x07devices\x94h\t)\x81\x94}\x94(h\x05h\x0c\x8c\x04_tag\x94h\x0c\x8c\x08expanded\x94\x88\x8c\x0c_predecessor\x94}\x94h\x06Ns\x8c\x0b_successors\x94\x8c\x0bcollections\x94\x8c\x0bdefaultdict\x94\x93\x94\x8c\x08builtins\x94\x8c\x04list\x94\x93\x94\x85\x94R\x94h\x06]\x94\x8c\x0edevices/Campus\x94as\x8c\x04data\x94N\x8c\x10_initial_tree_id\x94h\x06\x8c\x0csegment_type\x94\x8c\nTOPIC_ROOT\x94\x8c\x05topic\x94\x8c\x00\x94ubh\x1dh\t)\x81\x94}\x94(h\x05h\x1dh\x0f\x8c\x06Campus\x94h\x10\x88h\x11}\x94h\x06h\x0csh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94(\x8c\x18devices/Campus/Building1\x94\x8c\x18devices/Campus/Building2\x94\x8c\x18devices/Campus/Building3\x94esh\x1eNh\x1fh\x06h \x8c\rTOPIC_SEGMENT\x94h"\x8c\x06Campus\x94ubh+h\t)\x81\x94}\x94(h\x05h+h\x0f\x8c\tBuilding1\x94h\x10\x88h\x11}\x94h\x06h\x1dsh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94\x8c\x1edevices/Campus/Building1/Fake1\x94ash\x1eNh\x1fh\x06h h.h"\x8c\x10Campus/Building1\x94ubh7h\t)\x81\x94}\x94(h\x05h7h\x0f\x8c\x05Fake1\x94h\x10\x88h\x11}\x94h\x06h+sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94(\x8c3devices/Campus/Building1/Fake1/SampleWritableFloat1\x94\x8c*devices/Campus/Building1/Fake1/SampleBool1\x94esh\x1e}\x94(\x8c\rdriver_config\x94}\x94\x8c\x08interval\x94K<\x8c\x08timezone\x94\x8c\nUS/Pacific\x94\x8c\x0bdriver_type\x94\x8c\nfakedriver\x94\x8c\x19publish_breadth_first_all\x94\x89\x8c\x13publish_depth_first\x94\x89\x8c\x15publish_breadth_first\x94\x89\x8c\x06campus\x94\x8c\x06campus\x94\x8c\x08building\x94\x8c\x08building\x94\x8c\x04unit\x94\x8c\x0bfake_device\x94uh\x1fh\x06h \x8c\x06DEVICE\x94h"\x8c\x16Campus/Building1/Fake1\x94ubh@h\t)\x81\x94}\x94(h\x05h@h\x0f\x8c\x14SampleWritableFloat1\x94h\x10\x88h\x11}\x94h\x06h7sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(\x8c\nPoint Name\x94\x8c\x14SampleWritableFloat1\x94\x8c\x05Units\x94\x8c\x03PPM\x94\x8c\rUnits Details\x94\x8c\x111000.00 (default)\x94\x8c\x08Writable\x94\x8c\x04TRUE\x94\x8c\x0eStarting Value\x94\x8c\x0210\x94\x8c\x04Type\x94\x8c\x05float\x94\x8c\x05Notes\x94\x8c-Setpoint to enable demand control ventilation\x94uh\x1fh\x06h \x8c\x05POINT\x94h"\x8c+Campus/Building1/Fake1/SampleWritableFloat1\x94ubhAh\t)\x81\x94}\x94(h\x05hAh\x0f\x8c\x0bSampleBool1\x94h\x10\x88h\x11}\x94h\x06\x8c\x1edevices/Campus/Building1/Fake1\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(h]\x8c\x0bSampleBool1\x94h_\x8c\x08On / Off\x94ha\x8c\x06on/off\x94hc\x8c\x05FALSE\x94he\x8c\x04TRUE\x94hg\x8c\x07boolean\x94hi\x8c$Status indidcator of cooling stage 1\x94uh\x1fh\x06h hkh"\x8c"Campus/Building1/Fake1/SampleBool1\x94ubh,h\t)\x81\x94}\x94(h\x05h,h\x0f\x8c\tBuilding2\x94h\x10\x88h\x11}\x94h\x06\x8c\x0edevices/Campus\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94\x8c\x1edevices/Campus/Building2/Fake1\x94ash\x1eNh\x1fh\x06h h.h"\x8c\x10Campus/Building2\x94ubh\x86h\t)\x81\x94}\x94(h\x05h\x86h\x0f\x8c\x05Fake1\x94h\x10\x88h\x11}\x94h\x06h,sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94(\x8c3devices/Campus/Building2/Fake1/SampleWritableFloat1\x94\x8c*devices/Campus/Building2/Fake1/SampleBool1\x94esh\x1e}\x94(\x8c\rdriver_config\x94}\x94\x8c\x08interval\x94K<\x8c\x08timezone\x94\x8c\nUS/Pacific\x94\x8c\x0bdriver_type\x94\x8c\nfakedriver\x94\x8c\x19publish_breadth_first_all\x94\x89\x8c\x13publish_depth_first\x94\x89\x8c\x15publish_breadth_first\x94\x89\x8c\x06campus\x94\x8c\x06campus\x94\x8c\x08building\x94\x8c\x08building\x94\x8c\x04unit\x94\x8c\x0bfake_device\x94uh\x1fh\x06h hSh"\x8c\x16Campus/Building2/Fake1\x94ubh\x8fh\t)\x81\x94}\x94(h\x05h\x8fh\x0f\x8c\x14SampleWritableFloat1\x94h\x10\x88h\x11}\x94h\x06h\x86sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(\x8c\nPoint Name\x94\x8c\x14SampleWritableFloat1\x94\x8c\x05Units\x94\x8c\x03PPM\x94\x8c\rUnits Details\x94\x8c\x111000.00 (default)\x94\x8c\x08Writable\x94\x8c\x04TRUE\x94\x8c\x0eStarting Value\x94\x8c\x0210\x94\x8c\x04Type\x94\x8c\x05float\x94\x8c\x05Notes\x94\x8c-Setpoint to enable demand control ventilation\x94uh\x1fh\x06h hkh"\x8c+Campus/Building2/Fake1/SampleWritableFloat1\x94ubh\x90h\t)\x81\x94}\x94(h\x05h\x90h\x0f\x8c\x0bSampleBool1\x94h\x10\x88h\x11}\x94h\x06\x8c\x1edevices/Campus/Building2/Fake1\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(h\xab\x8c\x0bSampleBool1\x94h\xad\x8c\x08On / Off\x94h\xaf\x8c\x06on/off\x94h\xb1\x8c\x05FALSE\x94h\xb3\x8c\x04TRUE\x94h\xb5\x8c\x07boolean\x94h\xb7\x8c$Status indidcator of cooling stage 1\x94uh\x1fh\x06h hkh"\x8c"Campus/Building2/Fake1/SampleBool1\x94ubh-h\t)\x81\x94}\x94(h\x05h-h\x0f\x8c\tBuilding3\x94h\x10\x88h\x11}\x94h\x06\x8c\x0edevices/Campus\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94\x8c\x1edevices/Campus/Building3/Fake1\x94ash\x1eNh\x1fh\x06h h.h"\x8c\x10Campus/Building3\x94ubh\xd3h\t)\x81\x94}\x94(h\x05h\xd3h\x0f\x8c\x05Fake1\x94h\x10\x88h\x11}\x94h\x06h-sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94(\x8c3devices/Campus/Building3/Fake1/SampleWritableFloat1\x94\x8c*devices/Campus/Building3/Fake1/SampleBool1\x94esh\x1e}\x94(\x8c\rdriver_config\x94}\x94\x8c\x08interval\x94K<\x8c\x08timezone\x94\x8c\nUS/Pacific\x94\x8c\x0bdriver_type\x94\x8c\nfakedriver\x94\x8c\x19publish_breadth_first_all\x94\x89\x8c\x13publish_depth_first\x94\x89\x8c\x15publish_breadth_first\x94\x89\x8c\x06campus\x94\x8c\x06campus\x94\x8c\x08building\x94\x8c\x08building\x94\x8c\x04unit\x94\x8c\x0bfake_device\x94uh\x1fh\x06h hSh"\x8c\x16Campus/Building3/Fake1\x94ubh\xdch\t)\x81\x94}\x94(h\x05h\xdch\x0f\x8c\x14SampleWritableFloat1\x94h\x10\x88h\x11}\x94h\x06h\xd3sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(\x8c\nPoint Name\x94\x8c\x14SampleWritableFloat1\x94\x8c\x05Units\x94\x8c\x03PPM\x94\x8c\rUnits Details\x94\x8c\x111000.00 (default)\x94\x8c\x08Writable\x94\x8c\x04TRUE\x94\x8c\x0eStarting Value\x94\x8c\x0210\x94\x8c\x04Type\x94\x8c\x05float\x94\x8c\x05Notes\x94\x8c-Setpoint to enable demand control ventilation\x94uh\x1fh\x06h hkh"\x8c+Campus/Building3/Fake1/SampleWritableFloat1\x94ubh\xddh\t)\x81\x94}\x94(h\x05h\xddh\x0f\x8c\x0bSampleBool1\x94h\x10\x88h\x11}\x94h\x06\x8c\x1edevices/Campus/Building3/Fake1\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(h\xf8\x8c\x0bSampleBool1\x94h\xfa\x8c\x08On / Off\x94h\xfc\x8c\x06on/off\x94h\xfe\x8c\x05FALSE\x94j\x00\x01\x00\x00\x8c\x04TRUE\x94j\x02\x01\x00\x00\x8c\x07boolean\x94j\x04\x01\x00\x00\x8c$Status indidcator of cooling stage 1\x94uh\x1fh\x06h hkh"\x8c"Campus/Building3/Fake1/SampleBool1\x94ubu\x8c\x04root\x94h\x0c\x8c\x07_reader\x94X\x9c\x01\x00\x00devices\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Campus\n \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Building1\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Fake1\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SampleBool1\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 SampleWritableFloat1\n \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Building2\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Fake1\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SampleBool1\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 SampleWritableFloat1\n \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Building3\n \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Fake1\n \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SampleBool1\n \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 SampleWritableFloat1\n\x94ub.' +DEV_TREE = b'\x80\x04\x952\x11\x00\x00\x00\x00\x00\x00\x8c volttron.lib.topic_tree\x94\x8c\nDeviceTree\x94\x93\x94)\x81\x94}\x94(\x8c\x0b_identifier\x94\x8c$43dd9fcc-0066-11ec-afd5-f834419e14e3\x94\x8c\nnode_class\x94h\x00\x8c\nDeviceNode\x94\x93\x94\x8c\x06_nodes\x94}\x94(\x8c\x07devices\x94h\t)\x81\x94}\x94(h\x05h\x0c\x8c\x04_tag\x94h\x0c\x8c\x08expanded\x94\x88\x8c\x0c_predecessor\x94}\x94h\x06Ns\x8c\x0b_successors\x94\x8c\x0bcollections\x94\x8c\x0bdefaultdict\x94\x93\x94\x8c\x08builtins\x94\x8c\x04list\x94\x93\x94\x85\x94R\x94h\x06]\x94\x8c\x0edevices/Campus\x94as\x8c\x04data\x94N\x8c\x10_initial_tree_id\x94h\x06\x8c\x0csegment_type\x94\x8c\nTOPIC_ROOT\x94\x8c\x05topic\x94\x8c\x00\x94ubh\x1dh\t)\x81\x94}\x94(h\x05h\x1dh\x0f\x8c\x06Campus\x94h\x10\x88h\x11}\x94h\x06h\x0csh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94(\x8c\x18devices/Campus/Building1\x94\x8c\x18devices/Campus/Building2\x94\x8c\x18devices/Campus/Building3\x94esh\x1eNh\x1fh\x06h \x8c\rTOPIC_SEGMENT\x94h"\x8c\x06Campus\x94ubh+h\t)\x81\x94}\x94(h\x05h+h\x0f\x8c\tBuilding1\x94h\x10\x88h\x11}\x94h\x06h\x1dsh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94\x8c\x1edevices/Campus/Building1/Fake1\x94ash\x1eNh\x1fh\x06h h.h"\x8c\x10Campus/Building1\x94ubh7h\t)\x81\x94}\x94(h\x05h7h\x0f\x8c\x05Fake1\x94h\x10\x88h\x11}\x94h\x06h+sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94(\x8c3devices/Campus/Building1/Fake1/SampleWritableFloat1\x94\x8c*devices/Campus/Building1/Fake1/SampleBool1\x94esh\x1e}\x94(\x8c\rdriver_config\x94}\x94\x8c\x08interval\x94K<\x8c\x08timezone\x94\x8c\nUS/Pacific\x94\x8c\x0bdriver_type\x94\x8c\nfakedriver\x94\x8c\x19publish_breadth_first_all\x94\x89\x8c\x13publish_depth_first\x94\x89\x8c\x15publish_breadth_first\x94\x89\x8c\x06campus\x94\x8c\x06campus\x94\x8c\x08building\x94\x8c\x08building\x94\x8c\x04unit\x94\x8c\x0bfake_device\x94uh\x1fh\x06h \x8c\x06DEVICE\x94h"\x8c\x16Campus/Building1/Fake1\x94ubh@h\t)\x81\x94}\x94(h\x05h@h\x0f\x8c\x14SampleWritableFloat1\x94h\x10\x88h\x11}\x94h\x06h7sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(\x8c\nPoint Name\x94\x8c\x14SampleWritableFloat1\x94\x8c\x05Units\x94\x8c\x03PPM\x94\x8c\rUnits Details\x94\x8c\x111000.00 (default)\x94\x8c\x08Writable\x94\x8c\x04TRUE\x94\x8c\x0eStarting Value\x94\x8c\x0210\x94\x8c\x04Type\x94\x8c\x05float\x94\x8c\x05Notes\x94\x8c-Setpoint to enable demand control ventilation\x94uh\x1fh\x06h \x8c\x05POINT\x94h"\x8c+Campus/Building1/Fake1/SampleWritableFloat1\x94ubhAh\t)\x81\x94}\x94(h\x05hAh\x0f\x8c\x0bSampleBool1\x94h\x10\x88h\x11}\x94h\x06\x8c\x1edevices/Campus/Building1/Fake1\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(h]\x8c\x0bSampleBool1\x94h_\x8c\x08On / Off\x94ha\x8c\x06on/off\x94hc\x8c\x05FALSE\x94he\x8c\x04TRUE\x94hg\x8c\x07boolean\x94hi\x8c$Status indidcator of cooling stage 1\x94uh\x1fh\x06h hkh"\x8c"Campus/Building1/Fake1/SampleBool1\x94ubh,h\t)\x81\x94}\x94(h\x05h,h\x0f\x8c\tBuilding2\x94h\x10\x88h\x11}\x94h\x06\x8c\x0edevices/Campus\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94\x8c\x1edevices/Campus/Building2/Fake1\x94ash\x1eNh\x1fh\x06h h.h"\x8c\x10Campus/Building2\x94ubh\x86h\t)\x81\x94}\x94(h\x05h\x86h\x0f\x8c\x05Fake1\x94h\x10\x88h\x11}\x94h\x06h,sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94(\x8c3devices/Campus/Building2/Fake1/SampleWritableFloat1\x94\x8c*devices/Campus/Building2/Fake1/SampleBool1\x94esh\x1e}\x94(\x8c\rdriver_config\x94}\x94\x8c\x08interval\x94K<\x8c\x08timezone\x94\x8c\nUS/Pacific\x94\x8c\x0bdriver_type\x94\x8c\nfakedriver\x94\x8c\x19publish_breadth_first_all\x94\x89\x8c\x13publish_depth_first\x94\x89\x8c\x15publish_breadth_first\x94\x89\x8c\x06campus\x94\x8c\x06campus\x94\x8c\x08building\x94\x8c\x08building\x94\x8c\x04unit\x94\x8c\x0bfake_device\x94uh\x1fh\x06h hSh"\x8c\x16Campus/Building2/Fake1\x94ubh\x8fh\t)\x81\x94}\x94(h\x05h\x8fh\x0f\x8c\x14SampleWritableFloat1\x94h\x10\x88h\x11}\x94h\x06h\x86sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(\x8c\nPoint Name\x94\x8c\x14SampleWritableFloat1\x94\x8c\x05Units\x94\x8c\x03PPM\x94\x8c\rUnits Details\x94\x8c\x111000.00 (default)\x94\x8c\x08Writable\x94\x8c\x04TRUE\x94\x8c\x0eStarting Value\x94\x8c\x0210\x94\x8c\x04Type\x94\x8c\x05float\x94\x8c\x05Notes\x94\x8c-Setpoint to enable demand control ventilation\x94uh\x1fh\x06h hkh"\x8c+Campus/Building2/Fake1/SampleWritableFloat1\x94ubh\x90h\t)\x81\x94}\x94(h\x05h\x90h\x0f\x8c\x0bSampleBool1\x94h\x10\x88h\x11}\x94h\x06\x8c\x1edevices/Campus/Building2/Fake1\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(h\xab\x8c\x0bSampleBool1\x94h\xad\x8c\x08On / Off\x94h\xaf\x8c\x06on/off\x94h\xb1\x8c\x05FALSE\x94h\xb3\x8c\x04TRUE\x94h\xb5\x8c\x07boolean\x94h\xb7\x8c$Status indidcator of cooling stage 1\x94uh\x1fh\x06h hkh"\x8c"Campus/Building2/Fake1/SampleBool1\x94ubh-h\t)\x81\x94}\x94(h\x05h-h\x0f\x8c\tBuilding3\x94h\x10\x88h\x11}\x94h\x06\x8c\x0edevices/Campus\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94\x8c\x1edevices/Campus/Building3/Fake1\x94ash\x1eNh\x1fh\x06h h.h"\x8c\x10Campus/Building3\x94ubh\xd3h\t)\x81\x94}\x94(h\x05h\xd3h\x0f\x8c\x05Fake1\x94h\x10\x88h\x11}\x94h\x06h-sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94(\x8c3devices/Campus/Building3/Fake1/SampleWritableFloat1\x94\x8c*devices/Campus/Building3/Fake1/SampleBool1\x94esh\x1e}\x94(\x8c\rdriver_config\x94}\x94\x8c\x08interval\x94K<\x8c\x08timezone\x94\x8c\nUS/Pacific\x94\x8c\x0bdriver_type\x94\x8c\nfakedriver\x94\x8c\x19publish_breadth_first_all\x94\x89\x8c\x13publish_depth_first\x94\x89\x8c\x15publish_breadth_first\x94\x89\x8c\x06campus\x94\x8c\x06campus\x94\x8c\x08building\x94\x8c\x08building\x94\x8c\x04unit\x94\x8c\x0bfake_device\x94uh\x1fh\x06h hSh"\x8c\x16Campus/Building3/Fake1\x94ubh\xdch\t)\x81\x94}\x94(h\x05h\xdch\x0f\x8c\x14SampleWritableFloat1\x94h\x10\x88h\x11}\x94h\x06h\xd3sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(\x8c\nPoint Name\x94\x8c\x14SampleWritableFloat1\x94\x8c\x05Units\x94\x8c\x03PPM\x94\x8c\rUnits Details\x94\x8c\x111000.00 (default)\x94\x8c\x08Writable\x94\x8c\x04TRUE\x94\x8c\x0eStarting Value\x94\x8c\x0210\x94\x8c\x04Type\x94\x8c\x05float\x94\x8c\x05Notes\x94\x8c-Setpoint to enable demand control ventilation\x94uh\x1fh\x06h hkh"\x8c+Campus/Building3/Fake1/SampleWritableFloat1\x94ubh\xddh\t)\x81\x94}\x94(h\x05h\xddh\x0f\x8c\x0bSampleBool1\x94h\x10\x88h\x11}\x94h\x06\x8c\x1edevices/Campus/Building3/Fake1\x94sh\x13h\x16h\x19\x85\x94R\x94h\x06]\x94sh\x1e}\x94(h\xf8\x8c\x0bSampleBool1\x94h\xfa\x8c\x08On / Off\x94h\xfc\x8c\x06on/off\x94h\xfe\x8c\x05FALSE\x94j\x00\x01\x00\x00\x8c\x04TRUE\x94j\x02\x01\x00\x00\x8c\x07boolean\x94j\x04\x01\x00\x00\x8c$Status indidcator of cooling stage 1\x94uh\x1fh\x06h hkh"\x8c"Campus/Building3/Fake1/SampleBool1\x94ubu\x8c\x04root\x94h\x0c\x8c\x07_reader\x94X\x9c\x01\x00\x00devices\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Campus\n \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Building1\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Fake1\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SampleBool1\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 SampleWritableFloat1\n \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Building2\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Fake1\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SampleBool1\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 SampleWritableFloat1\n \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Building3\n \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 Fake1\n \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 SampleBool1\n \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 SampleWritableFloat1\n\x94ub.' def gen_response_codes(valid_codes: list, exclude: list = None):